当 Base class 具有受保护的析构函数时创建 unique_ptr<Base>

Creating unique_ptr<Base> when Base class has a protected destructor

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 向量编译?

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>();

"Polymorphic deleter" 基本上意味着 std::shared_ptr 存储指向销毁对象的函数的指针。 std::unique_ptr 使用 "static deleter" 直接销毁对象。这是一些伪代码:

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<Base>,因为你永远不需要写 std::make_shared<Base>()。只要 Base 的子类有 public 个析构函数,std::make_shared 就可以访问它们而不会出错。

局部变量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->destroy() 生成代码,您根本不需要访问 Base::~Base()shared_ptr 的默认构造函数只是将 deleter 设置为空指针:

shared_ptr() {
    deleter = nullptr;
}

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

让我们考虑这一行:

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

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

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

这里 shared_ptr_deleter 是一个具体的 class 派生自 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<Base>::destroy() 生成代码。现在它失败了,因为 Base::~Base() 无法访问。

(*) 我只提供简化的定义,只是为了展示基本思想,而没有深入讨论与理解所讨论问题无关的所有细节。