将放置新与存储类一起使用时的额外构造

Extra construction when using placement new with a storage class

本文关键字:一起 存储      更新时间:2023-10-16

在我想避免动态内存分配的情况下,我将 new 运算符替换为一个本质上使用某些静态分配对象的内存的进程(下面的Storage类(。您可以在下面看到一个最小工作示例:

#include <cassert>
#include <iostream>
struct Object { 
Object() { std::cout << "Creating a new objectn"; } 
static void *operator new(size_t);
static void operator delete(void *p);
};
static struct { 
Object where;
bool allocated = false;
} Storage; // 1
void *Object::operator new(size_t) { 
assert(!Storage.allocated);
auto p = ::new (&Storage.where) Object; // 2
Storage.allocated = true;

return p;
}
void Object::operator delete(void *p) { 
assert(Storage.allocated);
static_cast<Object *>(p)->~Object();
Storage.allocated = false;
}
int main() { Object *obj = new Object; } // 3

我的问题与对构造函数的调用次数有关。当我运行上述程序时,我希望调用构造函数两次(在上面的注释中标记为 1 和 2(,但我得到的输出是:

创建新对象

创建新对象

创建新对象

为什么构造函数被称为三次?我只期望通过静态对象和对放置 new 的调用进行构造函数调用。我尝试使用 gdb 跟踪代码,但这对我来说毫无意义,因为位置//3where对构造函数的第三次调用

。我想知道的原因是因为出现了一种情况,其中这个额外的构造函数调用会导致不必要的副作用;到目前为止,这个额外的调用还没有被注意到。

出于某种奇怪的原因,您的operator new在应该只分配内存时调用构造函数。这意味着对new的调用最终会调用Object的构造函数两次。operator new中有一个呼叫,main中有一个呼叫。

你可能想要这个:

void *Object::operator new(size_t) { 
assert(!Storage.allocated);
Storage.allocated = true;
return reinterpret_cast<void *> (&Storage.where);
}

想象一下,如果构造函数采用一个整数参数,并且main中的行如下所示:

Object *obj = new Object(7);

operator new如何知道如何正确构造对象?那不是你应该这样做的地方!

Object *obj = new Object;做两件事:

  1. 通过调用operator new来分配内存

  2. 调用构造函数。

您的operator new也调用构造函数,因此此语句调用构造函数两次(全局变量初始化调用一次(。

请注意,delete是相同的。delete obj;做两件事:

  1. 调用析构函数。

  2. 通过调用operator delete解除分配内存

您的operator delete也不应该调用析构函数,因为这样析构函数会被调用两次。