Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/136.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 为什么std::shared_ptr从基类和派生类调用析构函数,而delete只从基类调用析构函数?_C++_Shared Ptr - Fatal编程技术网

C++ 为什么std::shared_ptr从基类和派生类调用析构函数,而delete只从基类调用析构函数?

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 <

当第二个示例仅从基类调用析构函数时,为什么使用std::shared_ptr释放同时从基类和派生类调用析构函数

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