Why std::shared_ptr calls destructors from base and derived classes, where delete calls only destructor from base class? [duplicate]

故事扮演 提交于 2019-11-29 01:50:13

delete a is undefined behaviour, because the class Base does not have a virtual destructor and the "complete object" of *a (more accurately: the most-derived object containing *a) is not of type Base.

The shared pointer is created with a deduced deleter that deletes a Derived *, and thus everything is fine.

(The effect of the deduced deleter is to say delete static_cast<Derived*>(__the_pointer)).

If you wanted to reproduce the undefined behaviour with the shared pointer, you'd have to convert the pointer immediately:

// THIS IS AN ERROR
std::shared_ptr<Base> shared(static_cast<Base*>(new Derived));

In some sense, it is The Right Way for the shared pointer to behave: Since you are already paying the price of the virtual lookup for the type-erased deleter and allocator, it is only fair that you don't then also have to pay for another virtual lookup of the destructor. The type-erased deleter remembers the complete type and thus incurs no further overhead.

Matthieu M.

A missing piece to Kerrek SB's answer is how does the shared_ptr knows the type ?

The answer is that there are 3 types involved:

  • the static type of the pointer (shared_ptr<Base>)
  • the static type passed to the constructor
  • the actual dynamic type of the data

And shared_ptr does not know of the actual dynamic type, but knows which static type was passed to its constructor. It then practices type-erasure... but remembers somehow the type. An example implementation would be (without sharing):

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

Note that thanks to this mechanism, shared_ptr<void> is actually meaningful and can be used to carry any data an properly dispose of it.

Note also that there is a penalty involved with this semantics: the need for indirection required for the type-erasure of the deleter attribute.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!