You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

含保护虚析构函数的基类如何重置std::unique_ptr?

protected虚析构与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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 04:10:32