2

Following this question - Pure virtual call in destructor of most derived class - I tried some code to check some syntax and discovered that as sucessive destructors are called, they call their relevant virtual functions. Consider this code:

class Base
{
public:
virtual void Method() = 0;
};

class Derived :  public Base
{
public:
~Derived()
{
    Method();
}

virtual void Method()
{
    cout << "D";
}
};

class DoubleD : public Derived
{
public:

~DoubleD()
{
    Method();
}

virtual void Method()
{
    cout << "DD";
}
};

int main(array<System::String ^> ^args)
{
    DoubleD D;
    DoubleD E;
    return 0;
}

As expected, as the object gets destructed, it calls the correct method (eg first the most derived and then the the second most derived).

Ouput: DD D

My question is, why does this work? Since you are not meant to call virtual functions in a c'tor/d'tor, why does the virtual table "unwind" correctly.

Eg, I can see why the most derived one works, that was the state the virtual function pointer table was in when this started. But why, when Derived's destructor is called, does the table get correctly set to point at that classes implementation of Method.

Why not just leave it, or if it is being nice, set the value to NULL.

Community
  • 1
  • 1
T. Kiley
  • 2,752
  • 21
  • 30
  • 1
    "set the value to NULL"? Set *what* value to null?! Also note that there's no "vtable" in C++. That's an implementation detail, and if you already managed to learn about it, then you were probably really close to learning how destructors are implemented -- why did you stop reading? – Kerrek SB May 13 '13 at 20:46
  • On a side note, mark the destructor as virtual. It is good practice while using virtual functions. – bjskishore123 May 13 '13 at 20:49
  • 1
    "Since you are not meant to call virtual functions in a c'tor/d'tor": Who told you that? The advice is to be careful calling them, since they've been "unwound" (as you put it) and might not do what you expect. (And don't call them if they're pure virtual; that's not allowed.) – Mike Seymour May 13 '13 at 21:17

4 Answers4

6

Since you are not meant to call virtual functions in a c'tor/d'tor, why does the virtual table "unwind" correctly.

The premise is wrong. There's nothing wrong with calling virtual functions from a constructor or destructor, provided you know how they work. As you've seen, the dynamic type is the type of the constructor or destructor being run, so you don't get virtual calls to the parts of the object that haven't yet been constructed or have already been destroyed.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
  • I assumed that since everyone says don't call them in constructors/destructor that it was undefined behaviour, guess I should check things like that! – T. Kiley May 13 '13 at 21:06
  • @T.Kiley: Generally, one wouldn't say "Don't do X" if X wasn't allowed. If X wasn't allowed, one would say "X is not valid C++". Advice on what to do and what not to do normally only concerns things that valid in the language. It's sort of implicitly assumed that you must never do things that are *not* allowed. – Kerrek SB May 13 '13 at 21:16
  • @T.Kiley: I don't know who "everyone" is, but they're wrong. It's only undefined behaviour if the function is pure virtual. – Mike Seymour May 13 '13 at 21:18
  • To be fair to this undefined "everyone", I never explicitly saw anyone claim it was undefined, just numerous people giving the blanket answer to simply not do it :p – T. Kiley May 13 '13 at 22:21
  • @T.Kiley: As I said, "don't do it" is a good blanket answer for things like this, because it means "It's valid, but difficult to use correctly, and it's a bad idea. If you knew when it's OK for you to use it, you wouldn't be asking about what it does." The hidden message in this advice is that if you really want to know the details, you now at least know what to search for, so you can do your own research at your leisure. – Kerrek SB May 14 '13 at 07:54
3

The behaviour is perfectly well defined. You shouldn't worry about how your compiler vendor managed to implement it (though it's not very hard to reason out yourself, or just look up).

It's generally not advised to call virtual functions in the destructor because of the non-intuitive behaviour, but there's nothing fundamentally wrong with it.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
2

This is how it is supposed to work according to the standard.

As for why, after you've run the destructor for a derived class you can't count on any of the properties of that class to be valid or consistent. Calling one of the virtual methods at that point would be a disaster if it went into a derived class method.

It's quite likely that the compiler bypasses the vtable altogether, since it already knows which overridden method applies to the current state of the object. That's just an implementation detail though.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
1

Virtual table doesn't get modified at run time after the initial setup at object creation. On some implementations, Virtual table shall be created as per class basis.

In your example, when DoubleD object is destroyed, it calls method function in DoubleD class, Because, the DoubleD part of the object is not yet destroyed completely. VTable of DoubleD class has an entry for method function to point to method in its class as it is overridden(in the last level of inheritance)

Once DoubleD is destroyed, now the object type is of type Derived. So the call has to go to the method in vtable of class Derived. Hence the behavior.

bjskishore123
  • 6,144
  • 9
  • 44
  • 66
  • Oh wow so if you have a long chain of inheritance with the same function being over-ridden repeatedly, the size of the object is actually larger because each class has it's own copy of the v-table? Why is that? (is it purely for dealing with this case? Seems OTT and just define this as undefined behaviour) – T. Kiley May 13 '13 at 21:06
  • 3
    @T.Kiley: No, in most implementations, each _class_ has a table, but all instances/copies of that class _share_ a single table. Usually each instance/copy has a pointer to the table for the current-most-derived-type, and as it "unwinds" it changes the pointer to the next table. – Mooing Duck May 13 '13 at 21:13
  • 1
    @T.Kiley: Size of object is implementation dependent. Most implementations generally use this 'vtable per class' concept. All the object will have v-ptr to a shared v-table(of that class) – bjskishore123 May 13 '13 at 21:17
  • Ah I see, that makes sense, but why even change the pointer on the instance(I know this is all implementation stuff) -- it seems odd the standard would explicitly allow a destructor can call a virtual method when it can just call it explicitly (eg `Derived::Method()`)and get the same result. It is trivial, but it would save one assignment (per inheritance depth), no? – T. Kiley May 13 '13 at 22:25