含保护虚析构函数的基类如何重置std::unique_ptr?
问题描述
需要根据条件创建A或B类对象,两者均继承自基类Base,Base拥有protected的虚析构函数。将变量声明为std::unique_ptr<Base> member,通过std::make_unique<A>或std::make_unique<B>创建对象后,调用member.reset()时出现编译错误,提示无法访问protected析构函数。疑问点:既然析构函数是virtual的,为何编译器无法调用正确的派生类析构函数?
完整代码如下:
#include <iostream> #include <memory> using namespace std; class Base { public: Base() = default; protected: virtual ~Base() = default; private: int a; }; class A : public Base { }; class B : public Base { }; unique_ptr<Base> get_object(bool cond) { if (cond) { return make_unique<A>(); } else { return make_unique<B>(); } } int main() { cout << "Hello World"; bool cond; cin >> cond; unique_ptr<Base> a_or_b = get_object(cond); a_or_b.reset(); return 0; }
为什么虚析构函数在这里不起作用?
核心原因是C++的访问权限检查优先级高于虚函数的动态绑定解析。
当std::unique_ptr执行reset()或自身析构时,内部逻辑会尝试直接调用Base*指针的析构函数(即执行delete ptr,ptr为Base*类型)。此时编译器首先检查当前上下文(标准库unique_ptr的实现代码作用域)是否有权限访问Base的析构函数——由于Base的析构是protected的,外部作用域(包括标准库代码)没有访问权限,因此直接触发编译错误,根本不会进入虚函数动态绑定的阶段,自然无法调用到派生类的析构函数。
解决方法
方法1:自定义删除器(推荐)
在Base类中提供一个静态成员函数,用于销毁对象(Base作用域内可访问自身的protected析构),然后给std::unique_ptr指定这个自定义删除器:
修改后的代码示例:
#include <iostream> #include <memory> using namespace std; class Base { public: Base() = default; // 静态销毁函数,在Base作用域内可访问protected析构 static void destroy(Base* ptr) { delete ptr; } protected: virtual ~Base() = default; private: int a; }; class A : public Base { }; class B : public Base { }; // 定义带自定义删除器的unique_ptr类型别名 using BasePtr = unique_ptr<Base, decltype(&Base::destroy)>; BasePtr get_object(bool cond) { if (cond) { // 显式构造BasePtr,传入对象指针和删除器 return BasePtr(new A(), &Base::destroy); } else { return BasePtr(new B(), &Base::destroy); } } int main() { cout << "Hello World"; bool cond; cin >> cond; BasePtr a_or_b = get_object(cond); a_or_b.reset(); // 正常执行,删除器调用Base::destroy,触发虚析构 return 0; }
注意:这里无法使用std::make_unique,因为make_unique默认使用默认删除器,无法直接指定自定义删除器,需显式通过new创建对象并构造unique_ptr。
方法2:将std::unique_ptr的使用作用域声明为Base的友元
在Base类中添加友元声明,允许main函数或包含unique_ptr操作的作用域访问其protected析构:
class Base { public: Base() = default; // 允许main函数访问protected析构 friend int main(); protected: virtual ~Base() = default; private: int a; };
这种方法耦合性高,若后续有其他地方使用unique_ptr<Base>,需不断添加友元声明,不符合封装原则,不推荐在大型项目中使用。
方法3:将Base的析构函数改为public
如果设计上允许直接销毁Base类型的对象,可以将析构函数改为public:
class Base { public: Base() = default; virtual ~Base() = default; // 改为public private: int a; };
但如果原本设计意图是禁止直接销毁Base对象(只能通过派生类),这种方法会破坏设计约束,需谨慎使用。
内容的提问来源于stack exchange,提问作者Kartik Anand

