I was looking at this article, and it says "Upon entry to the base class destructor, the object becomes a base class object, and all parts of C++—virtual functions, dynamic_casts, etc.—treat it that way." Does this mean that the vptr has changed during destruction? How does that happen?
Does the vptr change during destruction?
2.4k views Asked by pythonic metaphor AtThere are 2 answers
The pedantic C++ answer is, of course, "The Standard doesn't say anything about vtbls or how polymorphism is implemented."
However, practically speaking, yes. The vtbl is modified before the body of the base class' destructor begins execution.
EDIT:
Here is how I used MSVC10 to see this happen for myself. First, the test code:
#include <string>
#include <iostream>
using namespace std;
class Poly
{
public:
virtual ~Poly();
virtual void Foo() const = 0;
virtual void Test() const = 0 { cout << "PolyTest\n"; }
};
class Left : public Poly
{
public:
~Left()
{
cout << "~Left\n";
}
virtual void Foo() const { cout << "Left\n"; }
virtual void Test() const { cout << "LeftTest\n"; }
};
class Right : public Poly
{
public:
~Right() { cout << "~Right\n"; }
virtual void Foo() const { cout << "Right\n"; }
virtual void Test() const { cout << "RightTest\n"; }
};
void DoTest(const Poly& poly)
{
poly.Test();
}
Poly::~Poly()
{ // <=== BKPT HERE
DoTest(*this);
cout << "~Poly\n";
}
void DoIt()
{
Poly* poly = new Left;
cout << "Constructed...\n";
poly->Test();
delete poly;
cout << "Destroyed...\n";
}
int main()
{
DoIt();
}
Now, set a breakpoint at the opening brace for the Poly
dtor.
When you run this code at it breaks on the opening brace (just before the body of the constructor begins executing), you can take a peek at the vptr:
Also, you can view the disassembly for the Poly
dtor:
Poly::~Poly()
{
000000013FE33CF0 mov qword ptr [rsp+8],rcx
000000013FE33CF5 push rdi
000000013FE33CF6 sub rsp,20h
000000013FE33CFA mov rdi,rsp
000000013FE33CFD mov ecx,8
000000013FE33D02 mov eax,0CCCCCCCCh
000000013FE33D07 rep stos dword ptr [rdi]
000000013FE33D09 mov rcx,qword ptr [rsp+30h]
000000013FE33D0E mov rax,qword ptr [this]
000000013FE33D13 lea rcx,[Poly::`vftable' (13FE378B0h)]
000000013FE33D1A mov qword ptr [rax],rcx
DoTest(*this);
000000013FE33D1D mov rcx,qword ptr [this]
000000013FE33D22 call DoTest (13FE31073h)
cout << "~Poly\n";
000000013FE33D27 lea rdx,[std::_Iosb<int>::end+4 (13FE37888h)]
000000013FE33D2E mov rcx,qword ptr [__imp_std::cout (13FE3C590h)]
000000013FE33D35 call std::operator<<<std::char_traits<char> > (13FE3104Bh)
}
000000013FE33D3A add rsp,20h
000000013FE33D3E pop rdi
000000013FE33D3F ret
Step over the next line, in to the body of the destructor, and take another peek at the vptr:
Now when we call DoTest
from within the body of the destructor, the vtbl has already been modified to point to purecall_
, which generates a runtime assertion error in the debugger:
In all implementations that use virtual function tables (i.e. all current C++ implementations) the answer is yes, the
vptr
changes to that of the type of the destructor that is being executed. The reason is that the standard requires that the type of the object being destructed is the type of the destructor being exectued.If you have a hierarchy of three types B, D, MD (base, derived, most derived) and you instantiate and destroy an object of type
MD
, then while executingMD::~MD()
the type of the object isMD
, but when the implicit call to the base destructor is executed, the runtime type of the object must beD
. This is achieved by updating thevptr
.