0

Lets say I have two simple classes like this with non-virtual destructors:

struct A
{
    ~A() { std::cout << "A destructor" << std::endl; }
}
struct B : A
{
    ~B() { std::cout << "B destructor" << std::endl; }
}

When an instance of B is destructed, the destructor of A is called as well. When I destruct an instance of B through a pointer of type A* then Bs destructor will not be called. Does this also count for explicitly calling the destructor in a way you would also call a normal member function?

struct A
{
    ~A() { std::cout << "Hello" << std::endl; }
    void f() { std::cout << "Hello" << std::endl; }
}

A a;
a.~A(); // case 1
a.f(); // case 2

In this example, is there any difference between the two cases other than the name of the function that is called?

EDIT: Consider the same example with CRTP:

template <typename C>
struct A
{
    ~A() { static_cast<C*>(this)->~C(); }
}
struct B : public A<B>
{
    ~B() { std::cout << "B destructor" << std::endl; }
}

A<B>* a = new B();
delete a;

Would this cause undefined behaviour or a memory leak?

  • 1
    `When I destruct an instance of B through a pointer of type A*` then your program exhibits undefined behavior. – Igor Tandetnik Sep 13 '15 at 14:01
  • 2
    `a.~A();` is by itself legal, but a) it ends the lifetime of `a`, so calling `a.f()` after that exhibits undefined behavior, and b) eventually `a` will go out of scope and the destructor will be called again - and that, too, exhibits undefined behavior. Long story short, while it's possible to call a destructor explicitly, it's very rarely a good idea. – Igor Tandetnik Sep 13 '15 at 14:03
  • 1
    @IgorTandetnik is it ever a good idea? – Alex Díaz Sep 13 '15 at 14:03
  • 2
    It is recommended to define a base class' destructor as virtual to prevent such explicit calls of the destructor of an object. A possible reason why you would want to explicitly call the destructor is if you want to override the delete operator or if you want to release an object allocated with a placement new. – KnightsWatch Sep 13 '15 at 14:04
  • 1
    @AlejandroDíaz Yes. `std::vector` and the like do that routinely. Placement new on `push_back` et al, explicit destruction on `pop_back` – Igor Tandetnik Sep 13 '15 at 14:04
  • @IgorTandetnik I am aware of that. My question concerns whether calling the destructor explicitly like I would call a member function does the same thing; i.e. call `A`s destructor when I explicitly call `B`s destructor, but not vice vesa. – Marcus Tyssen Sep 13 '15 at 14:04
  • Does ending the lifetime of `a` only concern cases where the destructor actually does some destructing work, like deleting an object that was allocated on the heap? If that doesn't happen, is it still undefined behaviour? The purpose of this is this: I don't want to use a virtual destructor, but I still want to destruct the child class through a reference to the parent class. I'm using CRTP, that way I know exactly what the child classes destructor is. – Marcus Tyssen Sep 13 '15 at 14:05
  • `call As destructor when I explicitly call Bs destructor, but not vice vesa` Yes, of course. `Does ending the lifetime of a only concern cases where the destructor actually does some destructing work` It concerns all cases where the destructor is non-trivial (a trivial destructor is essentially one that does no work at all, as would be the case with a plain old struct, say; the standard provides a formal definition). – Igor Tandetnik Sep 13 '15 at 14:08
  • yes, it will cause undefined behavior (see answer below) – KnightsWatch Sep 13 '15 at 14:31
  • You have a few questions in one here. _One question per question, please_. There are dupes for each one. – Lightness Races in Orbit Sep 13 '15 at 15:37

2 Answers2

0

In fact, there are cases (i.e.: when using pools to avoid constant memory allocation), where you would normally call the destructor as a normal function, otherwise it would lead to undefined behaviour when you try to use placement new again on that memory address.

T* Pool::alloc() {
    ...
    return new (memory) T();
}

void Pool::dealloc( T* memory ) {
    ...
    memory->~T();
}

Regarding to the non virtual destructor issue, if the destructor is not virtual, deleting a B object inside a pointer to A, would lead to undefined behaviour.

struct A
{
    ~A() { std::cout << "A destructor" << std::endl; }
};
struct B : A
{
    ~B() { std::cout << "B destructor" << std::endl; }
};

A* a = new B();
delete a; // undefined behaviour, just ~A() will be called

B* b = new B();
delete b; // it's OK because b is B*

More info in this FAQ

alesegdia
  • 548
  • 3
  • 14
0

If you want to explicitly call a destructor, it has to be called from an object of the same type as the destructor being called. Otherwise, this will yield undefined behavior:

In an explicit destructor call, the destructor name appears as a ~ followed by a type-name or decltypespecifier that denotes the destructor’s class type. The invocation of a destructor is subject to the usual rules for member functions (9.3); that is, if the object is not of the destructor’s class type and not of a class derived from the destructor’s class type (including when the destructor is invoked via a null pointer value), the program has undefined behavior.

Also note that if explicitly calling a destructor, at the end of the scope where the instance resides, the destructor will implicitly be called a second time, provided that the object has been instanciated on the stack. That also yields undefined behavior:

Once a destructor is invoked for an object, the object no longer exists; the behavior is undefined if the destructor is invoked for an object whose lifetime has ended (3.8). [ Example: if the destructor for an automatic object is explicitly invoked, and the block is subsequently left in a manner that would ordinarily invoke implicit destruction of the object, the behavior is undefined

I suggest you read section 12.4 of the spec. The working draft is freely available.

KnightsWatch
  • 114
  • 6