C ++使用placement new undefined行为构造对象两次? [英] C++ Is constructing object twice using placement new undefined behaviour?

查看:163
本文介绍了C ++使用placement new undefined行为构造对象两次?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到了一些令我恐惧的代码。
基本上遵循以下模式:

  class Foo 
{
public:
//默认构造函数
Foo():x(0),ptr(nullptr)
{
// do nothing
}

更有趣的构造函数
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]; //分配一个默认构造的Foo的数组

for(int i = 0; i {
new(fooArray + i)Foo ); // use placement new to initialise
}

// ... do stuff

delete [] fooArray;
}

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

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

尽管仍然存在可能的资源泄漏,但至少一个防御性程序员可能会认为在正常方法中检查先前的分配。



正在构建两次这样的实际未定义行为/

非法的标准或只是一个坏主意?

解决方案

一般来说, ,以这种方式使用placement new不是一个好主意。从第一个新的调用初始化器或调用初始化器而不是调用新的初始化器,都被认为比您提供的代码更好。



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


程序可以通过重用对象占用的存储
来结束任何对象的生命周期。或者通过为
显式调用析构函数,类类型的对象具有非平凡析构函数。对于具有非平凡析构函数的类类型的对象
,程序不需要
在对象占用的
被重用或释放之前明确地调用析构函数;然而,如果没有
显式调用析构函数,或者如果delete-expression(5.3.5)是
不用于释放存储,则析构函数不应该是
隐式调用, 依赖于副作用的任何程序,析构函数产生的
具有未定义的行为。


所以当这发生时:

  Foo * fooArray = new Foo [numFoos]; //分配一个默认构造的Foo的数组

for(int i = 0; i {
new(fooArray + i)Foo ); // use placement new to initialise
}

展示位置新操作将结束 Foo 就在那里,并在它的地方创建一个新的。在许多情况下,这可能是坏的,但考虑到你的析构函数的工作方式,这将是好的。



调用一个现有的对象的放置新的可能是未定义的行为,但它取决于具体的对象。



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



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



如果包含的 int 指针可能等于 nullptr ,可能需要删除,然后在现有对象上调用 new 会调用未定义的行为。


I have come across some code which has horrified me. Essentially it follows this pattern :

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;
}

This code has been in the code base for years and it would seem has never caused a problem. It's obviously a bad idea since someone could change the default constructor to allocate not expecting the second construction. Simply replacing the second constructor with an equivalent initialisation method would seem the sensible thing to do. eg.

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

Although still subject to possible resource leaks, at least a defensive programmer might think to check for prior allocations in a normal method.

My question is:

Is constructing twice like this actually undefined behaviour/ outlawed by standard or simply just a bad idea? If undefined behaviour can you quote or point me to right place to look in the standard?

解决方案

Generally, working with placement new in this way is not a good idea. Calling an initializer from the first new, or calling an initializer instead of placement new are both considered to be better form than the code you've provided.

However, in this case, the behaviour of calling placement new over an existing object is well defined.

A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression (5.3.5) is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.

So when this happens:

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
}

The placement new operation will end the lifetime of the Foo that was there, and create a new one in it's place. In many circumstances this could be bad, but given the way your destructor works, this will be fine.

Calling placement new on an existing object could be undefined behaviour, but it depends on the specific object.

This does not produce undefined behaviour, because you are not depending on the "side effects" produced by the destructor.

The only "side-effect" in the destructor of your object is to delete the contained int pointer, but in this case that object is never in a deletable state when placement new is called.

If it was possible for the contained int pointer to be equal to something other than nullptr and could possibly require deleting, then calling placement new over the existing object would invoke undefined behaviour.

这篇关于C ++使用placement new undefined行为构造对象两次?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆