是否允许对象在C++的生命周期内合法地更改其类型?

sha*_*oth 9 c++ polymorphism virtual-functions rtti visual-c++

我有这个代码:

class Class {
public:
    virtual void first() {};
    virtual void second() {};
};

Class* object = new Class();
object->first();
object->second();
delete object;
Run Code Online (Sandbox Code Playgroud)

用Visual C++ 10和/ O2编译并进行反汇编:

282:    Class* object = new Class();
00403953  push        4  
00403955  call        dword ptr [__imp_operator new (4050BCh)]  
0040395B  add         esp,4  
0040395E  test        eax,eax  
00403960  je          wmain+1Ch (40396Ch)  
00403962  mov         dword ptr [eax],offset Class::`vftable' (4056A4h)  
00403968  mov         esi,eax  
0040396A  jmp         wmain+1Eh (40396Eh)  
0040396C  xor         esi,esi  
283:    object->first();
0040396E  mov         eax,dword ptr [esi]  
00403970  mov         edx,dword ptr [eax]  
00403972  mov         ecx,esi  
00403974  call        edx  
284:    object->second();
00403976  mov         eax,dword ptr [esi]  
00403978  mov         edx,dword ptr [eax+4]  
0040397B  mov         ecx,esi  
0040397D  call        edx  
285:    delete object;
0040397F  push        esi  
00403980  call        dword ptr [__imp_operator delete (405138h)]  
Run Code Online (Sandbox Code Playgroud)

请注意,在00403968对象的地址开始(vptr存储的位置)被复制到esi寄存器中.然后在0040396E此地址用于检索vptr并且该vptr值用于检索地址first().然后,在00403976vptr被恢复和重新用于检索的地址second().

为什么vptr被检索两次?这个对象可能vptr在两次调用之间有所改变,还是仅仅是一个不优化?

Max*_*kin 9

为什么vptr被检索两次?这个对象可能在调用之间改变了它的vptr,还是只是一个欠优化?

考虑:

object->first();
Run Code Online (Sandbox Code Playgroud)

此调用可能会破坏对象并在同一块内存中创建一个新对象.因此,在此调用之后,不能对状态做出任何假设.例如:

#include <new>

struct Class {
    virtual void first();
    virtual void second() {}
    virtual ~Class() {}
};

struct OtherClass : Class {
    void first() {}
    void second() {}
};

void Class::first() {
    void* p = this;
    static_assert(sizeof(Class) == sizeof(OtherClass), "Oops");
    this->~Class();
    new (p) OtherClass;
}

int main() {
    Class* object = new Class();
    object->first();
    object->second();
    delete object;
}
Run Code Online (Sandbox Code Playgroud)

如果该函数是内联的和/或使用链接时代码生成,则编译器可以优化掉不必要的寄存器加载.


由于DeadMG和Steve Jessop注意到上面的代码表现出不确定的行为.根据C++ 2003标准的3.8/7:

如果在对象的生命周期结束之后并且在重用或释放对象占用的存储之前,则在原始对象占用的存储位置创建新对象,指向原始对象的指针,引用引用原始对象,或者原始对象的名称将自动引用新对象,并且一旦新对象的生命周期开始,就可以用来操纵新对象,如果:

  • 新对象的存储完全覆盖原始对象占用的存储位置,以及
  • 新对象与原始对象的类型相同(忽略顶级cv限定符),和
  • 原始对象的类型不是const限定的,如果是类类型,则不包含任何类型为const限定的非静态数据成员或引用类型,以及
  • 原始对象是类型为T的派生程度最高的对象(1.8),新对象是类型为T的派生程度最高的对象(也就是说,它们不是基类子对象).

上述代码不满足上述列表中的要求2.