【问题标题】:Extra construction when using placement new with a storage class将placement new 与存储类一起使用时的额外构造
【发布时间】: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)];”并且构造函数只被调用一次。

标签: c++ placement-new


【解决方案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; 做了两件事:

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

    2. 调用构造函数。

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

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

    1. 调用析构函数。

    2. 通过调用operator delete释放内存

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

    【讨论】:

    • @Someprogrammerdude 但该模式的重点是不在运行时实际分配内存
    • 哇,感谢额外的错误修复,我现在可能不得不更改所选答案
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-14
    • 2010-09-26
    • 2021-08-22
    • 2017-01-04
    • 1970-01-01
    • 1970-01-01
    • 2015-10-17
    相关资源
    最近更新 更多