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

时间:2013-12-27 14:59:19

标签: 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;
}

输出:

--------------------
Derived destructor
Base destructor
--------------------
Base destructor

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

2 个答案:

答案 0 :(得分:16)

delete a是未定义的行为,因为类Base没有虚拟析构函数和*a的“完整对象”(更准确地说:包含{{的最派生对象) 1}})不是*a类型。

共享指针是使用推导出的删除器删除Base创建的,因此一切正常。

(推断删除的效果是Derived *)。

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

delete static_cast<Derived*>(__the_pointer)

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

答案 1 :(得分: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

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

另请注意,此语义涉及到惩罚:deleter属性的类型擦除需要间接