将 placement new 与存储一起使用时的额外构造 class

Extra construction when using placement new with a storage class

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

#include <cassert>
#include <iostream>

struct Object { 
  Object() { std::cout << "Creating a new object\n"; } 
  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

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

Creating a new object

Creating a new object

Creating a new object

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

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

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

  1. 通过调用operator new

  2. 分配内存
  3. 调用构造函数。

你的operator new也调用了构造函数,所以这个语句调用了两次构造函数(一次是全局变量初始化)。

注意delete是一样的。 delete obj; 做了两件事:

  1. 调用析构函数。

  2. 通过调用 operator delete

  3. 释放内存

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

出于某些奇怪的原因,您的 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 怎么知道如何正确构造对象?那不是你应该做的地方!