C++ 为什么std::shared_ptr从基类和派生类调用析构函数,而delete只从基类调用析构函数?
当第二个示例仅从基类调用析构函数时,为什么使用std::shared_ptr释放同时从基类和派生类调用析构函数C++ 为什么std::shared_ptr从基类和派生类调用析构函数,而delete只从基类调用析构函数?,c++,shared-ptr,C++,Shared Ptr,当第二个示例仅从基类调用析构函数时,为什么使用std::shared_ptr释放同时从基类和派生类调用析构函数 class Base { public: ~Base() { std::cout << "Base destructor" << std::endl; } }; class Derived : public Base { public: ~Derived() { std::cout <
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;
}
我在这两种情况下都期望有相同的行为。
删除a
是未定义的行为,因为类Base
没有虚拟析构函数,并且*a
的“完整对象”(更准确地说:包含*a
的最派生对象)不是Base
类型
共享指针是用一个推断的删除器创建的,该删除器删除一个派生的*
,因此一切正常
(推导出的deleter的作用是说delete static\u cast(\u指针)
)
如果要使用共享指针重现未定义的行为,必须立即转换指针:
// THIS IS AN ERROR
std::shared_ptr<Base> shared(static_cast<Base*>(new Derived));
//这是一个错误
std::shared_ptr shared(静态_cast(新派生));
从某种意义上说,这是共享指针的正确行为方式:因为您已经为类型擦除的删除器和分配器支付了虚拟查找的费用,所以您不必再为析构函数的另一个虚拟查找支付费用,这才是公平的。类型擦除删除器记住完整的类型,因此不会产生更多的开销。Kerrek SB答案中缺少的一个部分是
共享\u ptr
如何知道类型
答案是有三种类型:
- 指针的静态类型(
)共享\u ptr
- 传递给构造函数的静态类型
- 数据的实际动态类型
shared\u ptr
不知道实际的动态类型,但知道传递给其构造函数的是哪个静态类型。然后进行类型擦除。。。但不知怎么的,我记得那类型。一个示例实现是(不共享):
模板
类简单\u ptr\u内部\u接口{
公众:
虚拟T*get()=0;
虚空析构函数()=0;
}; // 类简单\u ptr\u内部\u接口
模板
类simple_ptr_internal:公共simple_ptr_internal_接口{
公众:
简单的内部(T*p,dd):指针(p),删除器(std::move(D)){
虚拟T*get()重写{return pointer;}
虚空析构函数()重写{deleter(指针);}
私人:
T*指针;
D删除器;
}; // 类简单\u ptr\u内部
模板
简单类{
模板
结构默认删除程序{
void操作符()(T*T){delete static_cast(T);}
};
模板
使用DefaultInternal=simple\u ptr\u internal;
公众:
模板
simple_ptr(派生*d):内部(新的DefaultInternal{d}){
~simple_ptr(){this->destruct();}
私人:
void destruct(){internal->destruct();}
简单的内部接口*内部;
}; // 简单类
请注意,由于这种机制,shared_ptr
实际上是有意义的,可以用来携带任何数据并正确处理
还要注意,这种语义涉及到一个惩罚:删除属性的类型擦除需要间接寻址。基本析构函数一开始就不是虚拟的。您正在寻找麻烦…请参阅。这样您就不必再为析构函数的另一个虚拟查找付费。如果析构函数是虚拟的,
shared\u ptr
的实现将支付动态调度的成本。成本不是这样做的原因。如果删除程序没有编码原始指针的类型(可能是完整对象的类型,也可能不是完整对象的类型),则共享指针的其他功能将不起作用,例如别名子对象,或者使用不同的类型(基指针与派生指针)维护共享指针。最后一段充满了问题。顺便说一句,禁用deleter中的虚拟分派将导致在以下情况下出现未定义的行为:struct base{virtual~base();};派生结构:基{};base*p=新导出的;std::共享的ptr sp(p)
这可能是一个常见的用例(指向接口的指针被传递到一个库中,该库通过一个共享指针对其进行内部管理)@DavidRodríguez dribeas:也许我没有明确说明删除器没有禁用虚拟分派(对于删除表达式,这无论如何都是不可能的),但是,如果您只在共享指针中使用层次结构,则可以选择不需要虚拟析构函数。但是,对deleter delete函数的调用是虚拟的。因为它不能作为其具体类型存储到共享指针中,而共享指针的类型已被擦除。因此,对于任何类型的破坏,我们都会进行虚拟调用。和具有虚拟析构函数的类型的双虚拟间接寻址。一个用于deleter操作符()(或Matthieu代码中的destruct()函数),另一个用于delete
操作符。@v.oddou:是的,的确,shared_ptr
本身总是要为类型擦除付出代价。
// THIS IS AN ERROR
std::shared_ptr<Base> shared(static_cast<Base*>(new Derived));
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