C++ 使用放置新的未定义行为构造对象两次

C++ Is constructing object twice using placement new undefined behaviour?

本文关键字:对象 两次 未定义 C++      更新时间:2023-10-16

我遇到了一些让我感到恐惧的代码。本质上它遵循以下模式:

class Foo
{
  public:
    //default constructor
    Foo(): x(0), ptr(nullptr)  
    {
      //do nothing
    }
    //more interesting constructor
    Foo( FooInitialiser& init): x(0), ptr(nullptr) 
    {
      x = init.getX();
      ptr = new int;
    }
    ~Foo()
    {
      delete ptr;
    }
  private:
    int x;
    int* ptr;
};

void someFunction( FooInitialiser initialiser )
{
   int numFoos = MAGIC_NUMBER;
   Foo* fooArray = new Foo[numFoos];   //allocate an array of default constructed Foo's
   for(int i = 0; i < numFoos; ++i)
   {
       new( fooArray+ i) Foo( initialiser );    //use placement new to initialise
   }
    //... do stuff
   delete[] fooArray;
}

这段代码已经在代码库中存在多年,似乎从未引起过问题。这显然是一个坏主意,因为有人可以更改默认构造函数以分配不期望第二个构造。简单地用等效的初始化方法替换第二个构造函数似乎是明智的做法。

void Foo::initialise(FooInitialiser& init)
{
    x = init.getX();
    ptr = new int;
}

尽管仍然受到可能的资源泄漏的影响,但至少防御性程序员可能会考虑以正常方法检查先前的分配。

我的问题是:

像这样两次构造实际上是未定义的行为/被标准禁止还是只是一个坏主意?如果不确定的行为,您可以引用或指出我正确的位置来查看标准吗?

通常,以这种方式使用放置新位置不是一个好主意。从第一个 new 调用初始值设定项,或调用初始值设定项而不是放置 new,都被认为比您提供的代码更好。

但是,在这种情况下,在现有对象上调用放置 new 的行为是明确定义的。

程序可以通过重用存储来结束任何对象的生存期 对象占据的或通过显式调用析构函数 具有非平凡析构函数的类类型的对象。对于对象 具有非平凡析构函数的类类型,该程序不是 需要在存储之前显式调用析构函数 对象被重用或释放;但是,如果没有 显式调用析构函数,或者如果删除表达式 (5.3.5( 是 不用于释放存储,析构函数不得 隐式调用和任何依赖于副作用的程序 析构函数生成的行为未定义。

因此,当这种情况发生时:

Foo* fooArray = new Foo[numFoos];   //allocate an array of default constructed Foo's
for(int i = 0; i < numFoos; ++i)
{
    new( fooArray+ i) Foo( initialiser );    //use placement new to initialise
}

放置新操作将结束存在的Foo的生存期,并在其位置创建一个新操作。在许多情况下,这可能很糟糕,但考虑到析构函数的工作方式,这很好。

在现有对象上调用放置 new 可能是未定义的行为,但这取决于特定对象。

这不会产生未定义的行为,因为您不依赖于析构函数产生的"副作用"。

对象的析构函数中唯一的"副作用"是delete包含的int指针,但在这种情况下,当调用放置new时,该对象永远不会处于可删除状态。

如果包含的 int 指针可能等于 nullptr 以外的其他内容,并且可能需要删除,则在现有对象上调用放置new将调用未定义的行为。