为什么std :: shared_ptr从基类和派生类调用析构函数,其中delete只调用基类的析构函数?

Pio*_*ach 12 c++ shared-ptr

当第二个示例只调用基类中的析构函数时,为什么在使用std :: shared_ptr deallocation从基类和派生类调用析构函数?

class Base
{
public:
    ~Base()
    {
        std::cout << "Base destructor" << std::endl;
    }
};

class Derived : public Base
{
public:
    ~Derived()
    {
        std::cout << "Derived destructor" << std::endl;
    }
};

void virtual_destructor()
{
    {
        std::cout << "--------------------" << std::endl;
        std::shared_ptr<Base> sharedA(new Derived);
    }

    std::cout << "--------------------" << std::endl;
    Base * a = new Derived;
    delete a;
}
Run Code Online (Sandbox Code Playgroud)

输出:

--------------------
Derived destructor
Base destructor
--------------------
Base destructor
Run Code Online (Sandbox Code Playgroud)

在这两种情况下我都期待相同的行为.

Ker*_* SB 16

delete a是未定义的行为,因为该类Base没有虚拟析构函数,并且*a(更准确地说:包含最多派生的对象*a)的"完整对象" 不是类型Base.

共享指针是使用推导的删除器删除a创建的Derived *,因此一切都很好.

(演绎的删除者的效果就是说delete static_cast<Derived*>(__the_pointer)).

如果要使用共享指针重现未定义的行为,则必须立即转换指针:

// THIS IS AN ERROR
std::shared_ptr<Base> shared(static_cast<Base*>(new Derived));
Run Code Online (Sandbox Code Playgroud)

从某种意义上说,共享指针的行为方式是正确的:因为你已经为类型擦除的删除器和分配器付出虚拟查找的代价,所以你不必支付也是公平的用于析构函数的另一个虚拟查找.类型擦除的删除器记住完整类型,因此不会产生进一步的开销.


Mat*_* M. 6

Kerrek SB答案的一个缺失是如何shared_ptr知道类型

答案是涉及3种类型:

  • 指针的静态类型(shared_ptr<Base>)
  • 传递给构造函数的静态类型
  • 数据的实际动态类型

并且shared_ptr 不知道实际的动态类型,但知道哪个静态类型被传递给它的构造函数.然后它会练习类型擦除...但是记得某种类型.一个示例实现是(不共享):

template <typename T>
class simple_ptr_internal_interface {
public:
    virtual T* get() = 0;
    virtual void destruct() = 0;
}; // class simple_ptr_internal_interface

template <typename T, typename D>
class simple_ptr_internal: public simple_ptr_internal_interface {
public:
    simple_ptr_internal(T* p, D d): pointer(p), deleter(std::move(d)) {}

    virtual T* get() override { return pointer; }
    virtual void destruct() override { deleter(pointer); }

private:
    T* pointer;
    D deleter;
}; // class simple_ptr_internal

template <typename T>
class simple_ptr {
    template <typename U>
    struct DefaultDeleter {
        void operator()(T* t) { delete static_cast<U*>(t); }
    };

    template <typename Derived>
    using DefaultInternal = simple_ptr_internal<T, DefaultDeleter<Derived>>;

public:
    template <typename Derived>
    simple_ptr(Derived* d): internal(new DefaultInternal<Derived>{d}) {}

    ~simple_ptr() { this->destruct(); }

private:
    void destruct() { internal->destruct(); }

    simple_ptr_internal_interface* internal;
}; // class simple_ptr
Run Code Online (Sandbox Code Playgroud)

请注意,由于这种机制,shared_ptr<void>实际上是有意义的,可以用来携带任何数据,妥善处理它.

还要注意,这种语义涉及到一个惩罚:需要对属性进行类型擦除所需的间接寻址deleter.