我可以在构造函数调用之前设置成员变量吗?
Can I set a member variable before constructor call?
我开始实现一个基于ID的内存池,其中每个元素都有一个id,它基本上是向量中的一个索引。在这种特殊情况下,我在构造对象本身之前就知道索引,所以我认为我在调用构造函数之前设置了 ID。
一些细节
从基于 ID 的池中分配对象如下:
- 从池中分配空闲 ID
- 根据 ID 值获取内存地址
- 在内存地址上构造对象
- 设置对象的 ID 成员
并且释放基于该 ID
这是代码(感谢JROK(:
#include <new>
#include <iostream>
struct X
{
X()
{
// id come from "nothing"
std::cout << "X constructed with id: " << id << std::endl;
}
int id;
};
int main()
{
void* buf = operator new(sizeof(X));
// can I set the ID before the constructor call
((X*)buf)->id = 42;
new (buf) X;
std::cout << ((X*)buf)->id;
}
编辑
我在提升沙盒中找到了一个常用解决方案:sandbox Boost.Tokenmap
我可以在构造函数调用之前设置成员变量吗?
否,但您可以使用 ID 创建一个基类,该基类在其构造函数中设置 ID(例如,如果无法分配 ID,则会引发异常(。从该类派生,并且在派生类进入构造函数的那一刻,ID 已经设置好了。您还可以在另一个类中管理 id 生成 - 无论是在某种全局单例中,还是将 id manager 作为第一个参数传递给构造函数。
typedef int Id;
class IdObject{
public:
Id getId() const{
return id;
}
protected:
IdManager* getIdManager() ...
IdObject()
:id(0){
IdManager* manager = getIdManager();
id = manager->generateId();
if (!id)
throw IdException;
manager->registerId(id, this);
}
~IdObject(){
if (id)
getIdManager()->unregisterId(id, this);
}
private:
Id id;
IdObject& operator=(IdObject &other){
}
IdObject(IdObject &other)
:id(0){
}
};
class DerivedObject: public IdObject{
public:
DerivedObject(){
//at this point, id is set.
}
};
这种事情。
是的,你可以做你正在做的事情,但这真的不是一个好主意。根据标准,您的代码调用未定义的行为:
3.8 对象生存期 [basic.life]
对象的生存期是对象的运行时属性。一个对象被称为具有非平凡的初始化 如果它属于类或聚合类型,并且它或其成员之一由非普通构造函数初始化 默认构造函数。[注意:由简单的复制/移动构造函数初始化是非平凡的初始化。 尾注 ] 类型 T 对象的生存期从以下时间开始:
— 获得具有 T 型正确对齐和大小的存储,以及
— 如果对象具有非平凡的初始化,则其初始化完成。
T 类型的对象的生存期在以下情况下结束:
— 如果 T 是具有非平凡析构函数 (12.4( 的类类型,则析构函数调用开始,或者
— 对象占用的存储被重复使用或释放。
在对象的生存期开始之前,但在对象将占用的存储之后 已分配,或者在对象的生存期结束后,在对象占用的存储之前 重用或释放,指向对象将位于或曾经所在的存储位置的任何指针 可以使用,但只能以有限的方式使用。对于正在建造或销毁的物体,请参见 12.7。否则 此类指针是指分配的存储 (3.7.4.2(,并且使用该指针就像指针的类型为 void* 一样, 定义明确。这样的指针可以取消引用,但生成的左值只能在有限的范围内使用 方式,如下所述。在以下情况下,程序具有未定义的行为:
— 指针用于访问非静态数据成员或调用 对象
当代码调用未定义行为时,允许实现执行任何它想要的操作。在大多数情况下,什么都不会发生 - 如果你幸运的话,你的编译器会警告你 - 但偶尔结果将是意想不到的灾难性。
您可以使用连续数组作为基础存储来描述包含相同类型的 N 个对象的池。请注意,在这种情况下,您不需要为每个分配的对象存储整数 ID - 如果您有指向已分配对象的指针,则可以从数组中对象的偏移量派生 ID,如下所示:
struct Object
{
};
const int COUNT = 5; // allow enough storage for COUNT objects
char storage[sizeof(Object) * COUNT];
// interpret the storage as an array of Object
Object* pool = static_cast<Object*>(static_cast<void*>(storage));
Object* p = pool + 3; // get a pointer to the third slot in the pool
int id = p - pool; // find the ID '3' for the third slot
不可以,在调用对象的构造函数之前,不能在对象中设置任何内容。 但是,您有以下几种选择:
-
将 ID 传递给构造函数本身,以便它可以将 ID 存储在对象中。
-
在正在构造的对象前面分配额外的内存,将 ID 存储在该额外内存中,然后在需要时让对象访问该内存。
如果您知道对象的将来地址(您的方案就是这种情况(,那么是的,您可以执行此类操作。但是,它不是明确定义的行为,因此它很可能不是一个好主意(并且在每种情况下都不是好的设计(。虽然它可能会"正常工作"。
按照上面的评论使用std::map
更干净,并且没有附加UB的"ifs"和"when's"。
尽管写入已知的内存地址可能会"正常工作",但在运行构造函数之前不存在对象,因此使用其任何成员都是不好的。
一切皆有可能。没有编译器可能会做任何这样的事情,但是编译器可能会在运行构造函数之前将对象的存储设置为零,因此即使您没有设置 ID 字段,它仍然会被覆盖。你没有办法知道,因为你在做什么是不确定的。
在构造函数调用之前,是否有理由要执行此操作?
从基于 ID 的池中分配对象如下:
1) allocate a free id from the pool 2) get a memory address based on the id value 3) construct the object on the memory address 4) set the ID member of the object and the deallocation is based on that id
根据您的步骤,您将在构造函数之后设置 ID。
所以我想在调用构造函数之前设置了 ID。
我讨厌直言不讳,但你需要有一个比这更好的理由来涉足未定义的行为领域。请记住,作为程序员,我们一直在学习很多东西,除非绝对没有办法绕过它,否则我们需要远离雷区,未定义的行为就是其中之一。
正如其他人指出的那样,是的,你可以做到,但这就像说你可以做rm -rf /
根。并不意味着你应该:)
C很容易在自己的脚上开枪。C++使它更难,但当你这样做时,你会炸掉你的整条腿!— 比亚恩·斯特劳斯特鲁普
- 嵌套在类中时无法设置成员数据
- 为什么将一个结构的引用设置为等于另一个结构只会更改一个数据成员?
- 聚合初始化,将成员指针设置为同一结构成员
- 是否可以使用智能指针成员设置具有另一个结构的结构?
- 将 AlphaMode 成员设置为 DXGI_SWAP_CHAIN_DESC1 会使 CreateSwapChainFo
- 如何为类中可能无法计算的成员设置值
- 在运行时为随机分布类成员设置最小和最大边界?
- 是否可以为模板类的模板函数成员设置别名?
- 将常量引用成员设置为临时变量是否安全
- 对象超出范围后,引用成员设置为 0
- 为 QWidget 的私有成员设置样式表
- 将CPP类成员设置为SQLite DB中的列
- 将数组的成员设置为零
- 0-原子的初始化是否保证将值成员设置为0
- 将公共成员设置为只读
- 在模板化结构的所有实例中将成员设置为相同的值
- 如何将类成员设置为常量
- 将从std::string派生的类派生的类的数据成员设置为值
- 将继承的成员设置为静态
- 在析构函数中将类成员设置为空