vptr在销毁期间是否会发生变化?

pyt*_*hor 9 c++ destruction vptr

我正在看这篇文章,它说"在进入基类析构函数后,该对象成为基类对象,而C++的所有部分 - 虚函数,dynamic_casts等 - 以这种方式对待它." 这是否意味着vptr在破坏期间发生了变化?这是怎么发生的?

Dav*_*eas 11

在使用虚函数表(即所有当前C++实现)的所有实现中,答案是肯定的,vptr即对正在执行的析构函数类型的更改.其原因是,该标准要求被破坏的对象的类型被exectued析构函数的类型.

如果有三种类型的B,d,MD(碱,衍生,大多数衍生)的层次结构和实例化和破坏类型的对象MD,则在执行MD::~MD()该对象的类型 MD,但是当到基座析构所述隐式呼叫执行时,对象的运行时类型必须是 D.这是通过更新vptr.

  • 类型_must be_ right的原因是它是可观察的,通过在销毁期间调用虚函数.必须调用正确的覆盖,这可能与在破坏开始之前调用的覆盖不同,这意味着必须更新vptr. (3认同)
  • @ hl3mukkel:是的,除了相反的方向(从基数到大多数派生的) (2认同)

Joh*_*ing 6

当然,迂腐的C++答案是"标准没有说明vtbls或多态如何实现."

但实际上,是的.在基类的析构函数体开始执行之前修改vtbl.

编辑:

以下是我使用MSVC10来看待自己的情况.一,测试代码:

#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();
}
Run Code Online (Sandbox Code Playgroud)

现在,在Polydtor的左大括号处设置一个断点.

当你运行这段代码时,它会在开始大括号上打破(就在构造函数的主体开始执行之前),你可以看一看vptr:在此输入图像描述

此外,您可以查看Polydtor 的反汇编:

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  
Run Code Online (Sandbox Code Playgroud)

跳过下一行,进入析构函数体,然后再看一下vptr:

在此输入图像描述 现在,当我们DoTest从析构函数体内调用时,vtbl已经被修改为指向purecall_,这会在调试器中生成运行时断言错误:

  • 我不认为测试是决定性的,如果是它会指向完全奇怪,其中vtable机制将在析构函数中调度到一个函数,其指针永远不在vtable中(在破坏开始之前).这个特定测试的问题是你直接从析构函数调用虚拟指针,并且在那时因为编译器知道对象的确切类型(`Poly`)它不使用动态调度,而是静态调度.要修复测试,你可以创建一个函数,它接受`Poly&`并在其上调用`Test`,强制调度 (3认同)