【问题标题】:Creating unique_ptr<Base> when Base class has a protected destructor当基类具有受保护的析构函数时创建 unique_ptr<Base>
【发布时间】:2020-04-28 20:35:08
【问题描述】:
class Base {
public:
    Base() {}
    virtual void print()const = 0;
protected:
    virtual ~Base() { std::cout << "Base destructor\n\n"; }
};

int main()
{
    //std::vector<std::unique_ptr<Base>> v1; 
    //The line above won't compile because: 'Base::~Base': cannot access protected member declared in class 'Base'
    std::vector<std::shared_ptr<Base>> v2;
    return 0;
}

在创建向量时试图调用析构函数是什么?为什么它不会针对 unique_ptr 向量进行编译,而是针对 shared_ptr 向量进行编译?

【问题讨论】:

  • 这能回答你的问题吗? shared_ptr magic :)
  • 嘿,评论什么时候从可能重复变为这回答了你的问题吗:P
  • @appleapple 谢谢。你提到的问题很有帮助。 Kerndog73 和 Evg 的答案更直接地解决了我遇到的问题。
  • @LightnessRacesBY-SA3.0 谢谢,好吧,我不太喜欢新的措辞:/

标签: c++ shared-ptr destructor unique-ptr


【解决方案1】:

局部变量v1v2 具有自动存储期限,超出范围时会自动销毁。 std::vector 在这里无关紧要:在 vector::~vector() 内部,编译器将为元素析构函数生成代码。即使向量始终为空(这是运行时属性!),仍然必须生成此代码。所以让我们简化代码:

std::unique_ptr<Base> v1;
std::shared_ptr<Base> v2;

v1 超出范围时,它必须被销毁。编译器生成的析构函数归结为(*):

~unique_ptr() {
    delete ptr;
}

要为delete ptr 生成代码,编译器需要可访问的析构函数。它是受保护的,所以编译失败。

现在让我们看看v2。编译器也必须生成析构函数。但是shared_ptr 有一个用于托管对象的类型擦除删除器。这意味着托管对象的析构函数将被间接调用——通过一个虚函数:

struct shared_ptr_deleter_base {
    virtual void destroy() = 0;
    virtual ~shared_ptr_deleter_base() = default;
};

~shared_ptr() {
    // member shared_ptr::deleter has type shared_ptr_deleter_base*
    if (deleter)
        deleter->destroy();
}

要为deleter-&gt;destroy() 生成代码,您根本不需要访问Base::~Base()shared_ptr 的默认构造函数只是将 deleter 设置为空指针:

shared_ptr() {
    deleter = nullptr;
}

这就是std::shared_ptr&lt;Base&gt; v2; 编译的原因:不仅Base::~Base() 在运行时不被调用,编译器也不会在编译时生成任何调用。

让我们考虑这一行:

std::shared_ptr<Base> v2(new Base());

现在调用以下构造函数(注意它是一个模板,带有一个单独的参数U,可以不同于shared_ptr&lt;T&gt; 中的T):

template<class U>
shared_ptr(U* ptr) {
    deleter = new shared_ptr_deleter<U>(ptr);
}

这里的shared_ptr_deleter是一个派生自shared_ptr_deleter_base的具体类:

template<class T>
struct shared_ptr_deleter : shared_ptr_deleter_base {
    T* ptr;
    shared_ptr_deleter(T* p) : ptr(p) {}

    virtual void destroy() {
        delete ptr;
    }
};

要为采用new Base() 的构造函数生成代码,编译器必须为shared_ptr_deleter&lt;Base&gt;::destroy() 生成代码。现在它失败了,因为 Base::~Base() 无法访问。

(*) 我只提供简化的定义,只是为了展示基本概念,而不涉及与理解所讨论的问题无关的所有细节。

【讨论】:

    【解决方案2】:

    std::unique_ptr 无法访问Base 的析构函数,因为它是protectedstd::shared_ptr 使用多态删除器,因此 std::shared_ptr 只需要在创建新的 std::shared_ptr 时访问 Base 的析构函数。

    // this fails because the destructor of Base is inaccessible
    std::unique_ptr<Base> a;
    
    // this is ok because the destructor isn't required to instantiate the type
    std::shared_ptr<Base> b;
    
    // this fails because make_shared needs the destructor
    std::shared_ptr<Base> c = std::make_shared<Base>();
    

    “多态删除器”基本上意味着std::shared_ptr 存储了一个指向销毁对象的函数的指针。 std::unique_ptr 使用“静态删除器”直接销毁对象。这是一些伪代码:

    struct shared_ptr {
      ~shared_ptr() {
         deleter();
      }
    
      void (*deleter)(); // pointer to function that destroys the object
    };
    
    // shared_ptr doesn't try to call the destructor directly so we don't need access
    // so this is ok
    shared_ptr a;
    
    shared_ptr make_shared() {
      // here we generate (with templates) a function that calls Base::~Base
      // then we set "deleter" to point to that function
      // the destructor has to be accessible for us to do this
    }
    // so we get an error here
    shared_ptr b = make_shared();
    
    struct unique_ptr {
      ~unique_ptr() {
        // unique_ptr calls the Base destructor directly
        // unique_ptr needs access to the destructor to instantiate the type
      }
    };
    
    // so we get an error here
    unique_ptr c;
    

    在您的情况下,Base 恰好是抽象的,因此您可以使用 std::shared_ptr&lt;Base&gt;,因为您永远不需要编写 std::make_shared&lt;Base&gt;()。只要Base 的子类具有public 析构函数,std::make_shared 就可以访问它们而不会出现错误。

    【讨论】:

    • shared_ptr 构造函数(或make_shared())中,编译器不仅“设置”deleter 函数指针,而且还生成函数代码(删除器)它指向。对于std::shared_ptr&lt;Base&gt; b;,没有生成这样的代码,所以编译成功。
    猜你喜欢
    • 1970-01-01
    • 2013-11-03
    • 2015-07-09
    • 2012-09-08
    • 2015-10-09
    • 2011-10-23
    • 2014-08-29
    • 2018-09-23
    • 2011-03-15
    相关资源
    最近更新 更多