16

I'm having trouble understanding what's the reason for the crash in the following code :

class A {
public:
    virtual ~A() { goo(); }
    void goo() { absFoo(); }
    virtual void absFoo() = 0;

};

class B : public A {
public:
    void absFoo() { cout << "In B \n"; }
};

int main() 
{
    B b1;
    b1.goo();
}

The main prints "In B" as expected, but in the end when it crashes, I can't debug it either, the compiler pops a weird message.

So my question is, when the destructor of A calls "goo()", does "absFoo()", crash because we're referring to an abstract function?

Or does the compiler actually look for a definition in the derived classes? (And it doesn't exist anymore because it was destructed beforehand so it crashes)

I know that if we had called "absFoo()" directly from the destructor, the reason would have been the abstract function. But since here it's an outside function calling "absFoo()" I'm having trouble understanding the real reason.

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
Martin
  • 378
  • 2
  • 18
  • 10
    aside: main must return int, not void – Amadeus Feb 13 '18 at 13:50
  • 2
    You can't actually call `absFoo` directly from `A`'s destructor. The program won't compile in that case, period. – StoryTeller - Unslander Monica Feb 13 '18 at 13:50
  • @StoryTeller I know, but my question is what's the real reason for the crash out of the two – Martin Feb 13 '18 at 13:51
  • 4
    The reason is that it's illegal. And the language specification won't protect you from violating this rule. – StoryTeller - Unslander Monica Feb 13 '18 at 13:52
  • 2
    This looks similar to this other question https://stackoverflow.com/questions/12092933/calling-virtual-function-from-destructor – Jorge Y. Feb 13 '18 at 13:53
  • @JorgeY.In that example he didn't really call the abstract function in the destructor so I'm having trouble figuring this out.. – Martin Feb 13 '18 at 13:57
  • Probably if you look at the standard it would say something like "calling a virtual function from a destructor leads to undefined behavior". So whatever happens, like printing and then crashing, would fall under such definition. From a practical point of view, think about the situation in these terms: by calling a virtual function, you could end up executing code in base classes or derived classes, which destructors could already have been executed, so you could be running code in objects already destroyed – Jorge Y. Feb 13 '18 at 14:08
  • 3
    @Martin - You already *have* the answer in the question. :-) Hiding the `absFoo` call in a separate function makes no difference - when you enter A's destructor B is already gone and the dynamic type of `this` is `A*`. – Bo Persson Feb 13 '18 at 14:11
  • @BoPersson Thank you, that's exactly what I was looking for :) – Martin Feb 13 '18 at 14:33

3 Answers3

19

What happens when a destructor calls an abstract function

First, let us consider what happens when a destructor calls any virtual function (the same applies to the constructor by the way): When a virtual function foo is called in the destructor of T, the call will not be dispatched dynamically to an implementation in a derived type (the lifetime of any derived object has already ended), but statically to the implementation T::foo.

If T::foo is pure virtual, then calling it without dynamic dispatch will have undefined behaviour. That is what happens when a pure virtual function is (indirectly) called in a destructor (or constructor).

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Now I understand, my mistake was thinking that ''hiding'' the pure virtual function inside another function would've made any difference , but it doesn't even matter because it's statically implemented in this stage (destruction). Thanks! – Martin Feb 13 '18 at 14:33
  • 2
    Note: while in theory calling a pure virtual function is UB, in practice in the Itanium ABI I think it is guaranteed to generate an exception (which is quite better). – Matthieu M. Feb 13 '18 at 16:30
11

Just to complement the already accepted answer, this is the documentation from cppreference.

When a virtual function is called directly or indirectly from a constructor or from a destructor (including during the construction or destruction of the class’s non-static data members, e.g. in a member initializer list), and the object to which the call applies is the object under construction or destruction, the function called is the final overrider in the constructor’s or destructor’s class and not one overriding it in a more-derived class.

In other words, during construction or destruction, the more-derived classes do not exist.

amc176
  • 1,514
  • 8
  • 19
5

As the object is deconstructed, the vtable is updated to match the new status of the object.

Since you've removed the last function, the compiler will do whatever it considers fit; which in the case of a debug compilation in visual studio, will fallback to an abort reporting that a pure virtual function was called.

The vtable however is not part of the standard, it's an implementation detail, and there's no requirement for your program to crash; it's just what is considered the nicest thing to do when you've called a pure virtual function.

UKMonkey
  • 6,941
  • 3
  • 21
  • 30