我遇到了一些让我感到恐惧的代码。 基本上它遵循这种模式:
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;
}
虽然仍有可能的资源泄漏,但至少一名防御性程序员可能会考虑以正常方法检查先前的分配。
我的问题是:
正在构建两次这样的实际未定义的行为/标准禁止或仅仅是一个坏主意?如果未定义的行为可以引用或指向正确的位置以查看标准吗?
答案 0 :(得分:10)
通常情况下,以这种方式处理新位置不是一个好主意。从第一个新的调用初始化程序,或调用初始化程序而不是新的位置都被认为是比您提供的代码更好的形式。
但是,在这种情况下,很好地定义了在现有对象上调用new的行为。
程序可以通过重用存储来结束任何对象的生命周期 对象占用或显式调用析构函数 具有非平凡析构函数的类类型的对象。对于一个对象 对于具有非平凡析构函数的类类型,程序不是 需要在存储之前显式调用析构函数 对象占用被重用或释放;但是,如果没有 显式调用析构函数或者如果是delete-expression(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
的生命周期,并在其中创建一个新的操作。在许多情况下,这可能很糟糕,但考虑到你的析构函数的工作方式,这将没问题。
在现有对象上调用placement new可能是未定义的行为,但它取决于特定对象。
这不会产生未定义的行为,因为您不依赖于析构函数产生的“副作用”。
对象的析构函数中唯一的“副作用”是delete
包含的int
指针,但在这种情况下,当放置{{1}时,该对象永远不会处于可删除状态被称为。
如果包含的new
指针有可能等于int
以外的其他内容并且可能需要删除,那么在现有对象上调用placement nullptr
将调用未定义的行为