在不放置新运算符的情况下在预分配内存上使用虚函数初始化对象 - 这可能吗?如果不是,为什么

Initializing objects with virtual functions on pre-allocated memory without placement new operator - is that possible? If not, why

假设有一个简单的 class 层次结构,以及一个使用派生 class;

的状态对象
struct base_class {
    int Value;
    base_class() { this->Value = 1; }
    virtual void Func() { printf("Base\n"); };

};

struct derived_class : base_class {
    int Value;
    derived_class() { this->Value = 2; }
    void Func() { printf("Derived\n"); }
};

struct state {
    int a,b,c;
    derived_class Object;
};

现在,假设有一个分配器,它不知道类型,只是 returns 0 初始化分配的所需大小的块内存。

state *State = (state *)Allocate(sizeof(state));

现在,要正确初始化 vtable 指针,我们必须构造对象。 我已经看到它是通过放置新运算符完成的。它似乎确实有效。

但是,我很感兴趣为什么要像这样构造状态对象

*State = {};

State 已完美初始化,我看到值设置为 1 和 2。但是 _vfprt 为 0。即使我进入构造函数,this 指针似乎正确设置所有内容,_vfprt 指向正确的方法和所有内容。

但是当我从构造函数 return 时,_vfprt 无法复制到 State 对象。其他一切都在那里。但是 _vfprt 是 0; 所以我只是想知道是否有一个特殊的神奇复制构造函数在使用 new() 运算符时调用。还有如果有的话怎么用呢

我在我的应用程序中到处都使用这种初始化,老实说,为了支持一个小 class 到处都添加新的位置是一件很痛苦的事情。 {} 调用更简洁(也更短),它使分配调用变得更加容易。如果无法完成这项工作,我可以接受。我只是对为什么在我们从构造函数 return 之后没有复制 vtable 指针感到困惑。

如果有人能解释为什么会这样,那就太好了。 谢谢!

*state = {} 是赋值,不是构造。赋值不能更改对象的动态类型1。虚指针只依赖于对象的动态类型。所以没有必要在赋值中复制虚拟指针。

在赋值中,左侧的对象应该在其生命周期内。 placement new 表达式开始对象的生命周期,而赋值则不会。在赋值 *state = {} 中,编译器假设一个对象已经存在于 state 指向的内存位置。所以编译器假定虚拟指针已经被初始化。放置 new 将构造对象,该对象初始化虚拟指针。


1最派生对象的类型,这里是state.

您调用了未定义的行为!您通过此赋值 (*State = { };) 所做的等同于:(*State).operator=({ });。正如您所注意到的,您在生命周期从未开始的对象上调用函数(就像您调用 (*state).someFunction(); 一样),因为没有成功调用构造函数(好吧,根本没有被调用)。

深入了解一下:

由于您的对象是多态的,因此它接收到一个指向虚函数的指针 table。但是,一旦构造了一个对象,该指针肯定不会再改变(对象只要存在就不能改变它们的类型)。所以赋值运算符不需要改变它!所以指向 vtable 的指针只安装在构造函数中,但由于您从未调用过它,所以它根本不会安装。

这将适用于 class 本身(在没有 vtable 的给定情况下)以及成员或基础 classes(对于所有这些被递归调用的赋值运算符也遇到同样的问题。