我可以在构造函数调用之前设置成员变量吗?

Can I set a member variable before constructor call?

本文关键字:成员 设置 变量 函数调用 我可以      更新时间:2023-10-16

我开始实现一个基于ID的内存池,其中每个元素都有一个id,它基本上是向量中的一个索引。在这种特殊情况下,我在构造对象本身之前就知道索引,所以我认为我在调用构造函数之前设置了 ID。

一些细节

从基于 ID 的池中分配对象如下:

  1. 从池中分配空闲 ID
  2. 根据 ID 值获取内存地址
  3. 在内存地址上构造对象
  4. 设置对象的 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

不可以,在调用对象的构造函数之前,不能在对象中设置任何内容。 但是,您有以下几种选择:

  1. 将 ID 传递给构造函数本身,以便它可以将 ID 存储在对象中。

  2. 在正在构造的对象前面分配额外的内存,将 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++使它更难,但当你这样做时,你会炸掉你的整条腿!— 比亚恩·斯特劳斯特鲁普