Why simple destructor does not delete the derived object if declared using base pointer

后端 未结 4 1925
清酒与你
清酒与你 2021-01-25 12:11
int main()
{

    Base *p = new Derived;
    delete p;
    return 0;
}

I have some confusion, why deleting p here won\'t delete the derived object ? Is

4条回答
  •  囚心锁ツ
    2021-01-25 12:31

    There are many questions in one, and the C++ syntax is unfortunately misleading, so let's review our bases.


    What happens in a destructor ?

    When an object is destructed the language calls its destructor; the following sequence of events happen, in order:

    • the body of the destructor is executed
    • the attributes of the objects are destructed one at a time, in reverse order of construction (and thus declaration)
    • the base objects are destructed one at a time, in reverse order of construction (and thus declaration)

    Note: virtual bases are destructed before attributes, but after the body is executed.

    The important point, here, is that even though a Derived method hides its Base counterpart (if any), in the case of constructor and destructor the base counterparts are automatically called for you at a well-defined point over which you have no control.


    What of virtual destructors ?

    When a Base has a virtual destructor, the implicitly-declared or user-declared destructor in a Derived class will naturally override it. Similarly to other virtual methods it mean that when the destructor is called unqualified (that is, not as in b.Base::foo()) the call is in fact dispatched to the final-overider, that is the destructor of the most derived object (the real dynamic type of the object).

    However, as seen in the previous point, this does not mean that the Base destructor itself will never run because destructors are special; you could think of the Derived destructor as being (automatically) implemented as:

    Derived::~Derived(): ~Base(), ~attr0(), ~attr1() { ... }
    

    with the code being executed right-to-left.


    And what of a delete expression on a Base* ?

    Well, many people will think that Base* b = ...; delete b; is desugared as:

    // NOT QUITE
    Base* b = ...;
    b.~Base(); // possibly virtual destructor
    operator delete(&b);
    

    However this is in fact incorrect. The issues are that:

    • a Derived object address might differ from its Base subobject, and yet operator delete must be called with the exact pointer value that operator new returned.
    • the operator delete invoked (which can be overloaded) must be resolved at the definition point of the destructor

    Therefore, compilers need implement some magic; which is up to them. For example, compilers implementing the Itanium ABI (gcc, icc, clang, ...) will add a special entry to the v-table that contains a magic function that performs the pointer adjustments before calling the destructor of the most derived object and invoke operator delete with the right address. It could be seen as:

     class Derived: public Base {
     public:
         virtual ~Derived() override {}
    
         // FOR ILLUSTRATION PURPOSE ONLY
         // DON'T DO THIS AT HOME:
         // - you are forbidden to use `__` in your identifiers
         // - you are forbidden to call `delete this;` or any similar statement
         // FOR ILLUSTRATION PURPOSE ONLY
         virtual void __automagic_delete() {
             this->Derived::~Derived();
             operator delete(this);
         }
     };
    

    So, what if Base::~Base is not virtual ?

    Well, formally this is undefined behavior.

    In practice, two problems arise regularly:

    1. Because Derived::~Derived is not called, any resource held by its attributes or another of its base classes are not released. This may cause memory leaks, file descriptor leaks, connector leaks, deadlocks, ...
    2. If the Derived object has a different address than its Base subobject, then operator delete is invoked with an incorrect address... which in stringent implementation immediately leads to an abort and in less stringent ones might cause memory corruptions

    But of course, since this is undefined behavior, really anything could happen, so this is just the tip of the iceberg.

提交回复
热议问题