我开始实现一个基于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;
}
我在boost沙箱中找到了一个库存解决方案: sandbox Boost.Tokenmap
答案 0 :(得分:2)
我可以在构造函数调用之前设置成员变量吗?
不,但您可以创建一个具有ID的基类,该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.
}
};
这种事情。
答案 1 :(得分:1)
不,在调用构造函数之前,不能在对象中设置任何内容。但是,您有几个选择:
将ID传递给构造函数本身,因此它可以将ID存储在对象中。
在正在构造的对象前面分配额外的内存,将ID存储在额外的内存中,然后让对象在需要时访问该内存。
答案 2 :(得分:1)
如果您知道对象的待定地址(对于您的场景就是这种情况),那么您可以执行此类操作。但是,它没有明确定义的行为,所以它很可能不是一个好主意(并且在每种情况下都不是好的设计)。虽然它可能“工作正常”。
使用上面评论中建议的std::map
更清晰,并且没有附加UB的“ifs”和“whens”。
尽管写入已知的内存地址可能会“正常工作”,但在构造函数运行之前,对象不存在,因此使用其任何成员都是错误的mojo。 一切皆有可能。没有编译器可能会做任何这样的事情,但编译器可能会在运行构造函数之前将对象的存储设置为零,因此即使您没有设置ID字段,它仍然会被覆盖。你无从知晓,因为你所做的事情是未定义的。
答案 3 :(得分:1)
是的,你可以做你正在做的事情,但这不是一个好主意。根据标准,您的代码会调用Undefined Behaviour:
3.8对象生命期[basic.life]
对象的生命周期是对象的运行时属性。 据说一个对象具有非平凡的初始化 如果它是一个类或聚合类型,它或它的一个成员是由一个非常重要的构造函数初始化的 默认构造函数。 [注意:通过简单的复制/移动构造函数进行初始化是非平凡的初始化。 - 结尾注释] 类型T对象的生命周期始于:
- 获得具有类型T的正确对齐和大小的存储,
- 如果对象具有非平凡的初始化,则其初始化已完成。
类型T的对象的生命周期在以下时间结束:
- 如果T是具有非平凡析构函数(12.4)的类类型,则析构函数调用开始,或
- 重用或释放对象占用的存储空间。
在对象的生命周期开始之前但在对象将占用的存储之后 已分配,或者在对象的生命周期结束后以及对象占用的存储之前 重用或释放,任何指向对象所在或存在的存储位置的指针 可以使用,但只能以有限的方式使用。对于正在建造或销毁的物体,见12.7。除此以外, 这样的指针指的是已分配的存储(3.7.4.2),并且使用指针就像指针的类型为void *一样, 定义明确。可以取消引用这样的指针,但是所得到的左值可以仅在有限的情况下使用 方式,如下所述。 如果出现以下情况,该程序会有未定义的行为:
- 指针用于访问非静态数据成员或调用非静态成员函数 对象强>
当您的代码调用Undefined Behavior时,允许实现执行任何想要的操作。在大多数情况下,什么都不会发生 - 如果你很幸运,你的编译器会警告你 - 但偶尔结果会出乎意料地是灾难性的。
使用连续数组作为底层存储,描述相同类型的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
答案 4 :(得分:1)
在构造函数调用之前,您是否有理由这样做?
从基于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 ++让它变得更难,但是当你这样做时,你会把你的整条腿吹走! - Bjarne Stroustrup