0

假设有一个简单的类层次结构和一个使用派生类的状态对象;

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;
};

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

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

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

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

*State = {};

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

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

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

如果有人能解释为什么会发生这种情况,那就太好了。谢谢!

4

2 回答 2

2

*state = {}是一个任务,而不是一个结构。赋值不能更改对象上的动态类型1。虚拟指针仅取决于对象的动态类型。所以没有必要在赋值中复制虚拟指针。

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


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

于 2019-09-15T07:06:58.093 回答
2

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

在引擎盖下偷看一下:

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

这将适用于类本身(尽管在没有 vtable 的情况下)以及成员或基类(对于所有这些,递归调用的赋值运算符都会遇到相同的问题)。

于 2019-09-15T07:14:40.397 回答