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

后端 未结 4 1934
清酒与你
清酒与你 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:23

    Deletion of an object via a pointer to its base class is undefined unless the base has a virtual destructor. Even with a virtual destructor, the order of deletion may not be what immediately comes to mind.

    For more information, see this destructor reference.

    0 讨论(0)
  • 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.

    0 讨论(0)
  • 2021-01-25 12:34

    If you are asking about the behavior in situation when base class has no virtual destructor, then your confusion is stemming for the fact hat you already have a number of pre-conceived misconceptions about the behavior of this delete expression

    "It will only be able to delete the base class part of the object"

    "This will cause memory leaks"

    None of this makes any sense. There's no memory leaks here, and there's nothing as deterministic as being able to "delete the base class part" of it.

    If the base class has no virtual destructor the behavior of such code is simply undefined. There are quite a few different ways that undefined behavior can manifest itself, including but not limited to improper destructor call, improper operator delete selection, heap corruption and, yes, "memory leaks", both direct and indirect. There are many different things that can get screwed up in this case. Not just some "memory leaks", as the popular misconception makes people believe. (Where does that popular bit about "memory leaks" come from. Does anyone know?)

    So, you do need virtual destructor here. And the full list of reasons you need it can get quite long, if one decides to make a full effort to analyze it exhaustively. But in any case, this is an implementation detail. There's no specific explanation of what will really happen without tying it to a specific implementation.

    As for the "conceptual" explanation... There's always the most obvious one: it is, of course, necessary to invoke the proper destructor in order to performs the proper destruction. Even if we simply limit out consideration to user-defined destruction steps (i.e. what the user explicitly wrote in their derived-class destructor), we still need destructor polymorphism to properly invoke that destructor.

    However, there are also a number of other internal reasons. For example, in a typical implementation selection of the proper operator delete for raw memory deallocation also piggybacks on the virtuality of the destructor (see here, for example)

    0 讨论(0)
  • 2021-01-25 12:46

    You should declare a virtual destructor:

    class Base {
       //etc...
       virtual ~Base();
    };
    
    class Derived {
       //etc...
       virtual ~Derived();
    };
    
    Base* p = new Derived();
    delete p;
    

    (Of course many things are missing above, including constructors)

    0 讨论(0)
提交回复
热议问题