在不放置新运算符的情况下在预分配内存上使用虚函数初始化对象 - 这可能吗?如果不是,为什么
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(对于所有这些被递归调用的赋值运算符也遇到同样的问题。
假设有一个简单的 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(对于所有这些被递归调用的赋值运算符也遇到同样的问题。