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

Posted

技术标签:

【中文标题】将placement new 与存储类一起使用时的额外构造【英文标题】:Extra construction when using placement new with a storage class 【发布时间】:2020-01-21 10:18:35 【问题描述】:

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

#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 上述程序时,我希望调用构造函数两次(在上面的 cmets 中标记为 1 和 2)但我得到的输出是:

创建一个新对象

创建一个新对象

创建一个新对象

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

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

【问题讨论】:

@Jarod42 编号的 cmets 是断点告诉我调用的来源。一个来自静态初始化,一个在placement new;问题是:第三个是什么? @Someprogrammerdude 这是#0 Object::Object (this=0x555555756160 &lt;Storage&gt;) at placement.cpp:7 #1 0x0000555555554a4f in main () at placement.cpp:31(第7行是cout,第31行是main中的单行) 不应该placement new 运算符 not(!) 为对象调用placement new 吗?所以第一个是Storage::where,第二个是operator new,第三个是main-function。 operator new函数应该做什么是一个误解:它应该只分配内存,而不是构造对象。因此它不应该做placement-new。 附带说明(因为您的问题已经得到解答),您不需要静态分配对象,将其替换为“char buf[sizeof(Object)];”并且构造函数只被调用一次。 【参考方案1】:

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

【讨论】:

【参考方案2】:

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

    通过调用operator new分配内存

    调用构造函数。

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

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

    调用析构函数。

    通过调用operator delete释放内存

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

【讨论】:

@Someprogrammerdude 但该模式的重点是不在运行时实际分配内存 哇,感谢额外的错误修复,我现在可能不得不更改所选答案

以上是关于将placement new 与存储类一起使用时的额外构造的主要内容,如果未能解决你的问题,请参考以下文章

将 Querydsl 与 Spring Data 一起使用时的最佳实践

C++内存分配方法new与placement new使用方法详解

C++中的newoperator new与placement new

placement new的用法及用途

Placement new的用法及用途

内存管理——placement new