为什么在第二个示例只调用基类的析构函数时,使用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
我在两种情况下都期待相同的行为。
答案 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
属性的类型擦除需要间接。