Does the vptr change during destruction?

2.4k views Asked by At

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?

2

There are 2 answers

4
David Rodríguez - dribeas On BEST ANSWER

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 executing MD::~MD() the type of the object is MD, but when the implicit call to the base destructor is executed, the runtime type of the object must be D. This is achieved by updating the vptr.

2
John Dibling On

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:enter image description here

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:

enter image description here 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: