【问题标题】:placement new and delete放置新和删除
【发布时间】:2011-10-10 16:06:12
【问题描述】:

删除这里分配的所有内存的正确方法是什么?

  const char* charString = "Hello, World";
  void *mem = ::operator new(sizeof(Buffer) + strlen(charString) + 1);
  Buffer* buf = new(mem) Buffer(strlen(charString));

  delete (char*)buf;

  const char* charString = "Hello, World";
  void *mem = ::operator new(sizeof(Buffer) + strlen(charString) + 1);
  Buffer* buf = new(mem) Buffer(strlen(charString));

  delete buf;

还是两者都一样?

【问题讨论】:

  • 虽然这是一个完全有效的问题,但更简单的方法是在Buffer 内重载operator newoperator delete 以分配额外空间,然后编写类似new (strlen(charString) + 1) Buffer;

标签: c++ visual-c++ new-operator placement


【解决方案1】:

正确的方法是:

buf->~Buffer();
::operator delete(mem);

您只能使用delete operator 删除您从new operator 收到的内容。如果直接调用operator new函数,也必须直接调用operator delete函数,也必须手动调用析构函数。

【讨论】:

  • 实际上应该是::operator delete(mem); -- 而(void *)buf 在大多数实现中与mem 相同,除非Buffer 是POD 类型,否则不能保证相同
  • @bdonlan:如果 Buffer 不拥有任何资源(堆),“删除 buf”是否会泄漏内存?析构函数确实在这里被调用,但它是否只释放“sizeof(buf)”内存?
  • delete buf 是未定义的行为。 The delete-expression operator destroys a most derived object (1.8) or array created by a new-expression.。由于::operator new 不是新表达式,因此您违反了约束,因此未定义行为。
  • 但是new(mem) Buffer(strlen(charString)) 是一个新表达式
  • @aschepler:是的,请看stackoverflow.com/questions/4418220/…
【解决方案2】:

C++ 中有两个独立的概念:

  1. 新增/删除运算符

  2. 新建/删除表达式

运算符分配和释放内存。 new 表达式构造对象。 delete 表达式有时会破坏对象并调用运算符。

为什么是“有时”?因为它取决于表达。裸露的、全局的new首先调用operator-new分配内存,然后构造对象;全局 delete 调用析构函数并释放内存。但是newdelete 的所有其他重载都不同:

  • 重载的 new 表达式 调用重载的 new operator 来分配内存,然后继续构造对象。
  • 但是,没有重载删除表达式之类的东西,特别是没有“放置删除”:相反,您必须手动调用析构函数。

New/Delete 运算符仍然必须在匹配对中重载,因为当对象构造函数抛出异常时会调用匹配的删除运算符。但是,对于使用重载的new 运算符分配的对象,没有自动调用析构函数的方法,因此您必须自己执行此操作。

作为第一个也是最基本的示例,考虑placement-new 运算符,它被强制采用void * operator new (size_t, void * p) throw() { return p; } 的形式。因此,匹配的delete 运算符被要求什么都不做:void operator delete (void *, void *) throw() { }。用法:

void * p = ::operator new(5); // allocate only!
T * q = new (p) T();          // construct
q->~T();                      // deconstruct: YOUR responsibility
// delete (p) q;   <-- does not exist!! It would invoke the following line:
::operator delete(p, q);      // does nothing!
::operator delete(q);         // deallocate

【讨论】:

  • @thb:我看到了你的问题。不过,我不确定你是否准确地表达了这种情况:-S
  • @thb:在仔细检查了你的意思之后,我可以向你保证,这与这个问题或我的答案无关。
  • 好的。谢谢。删除我的 cmets。
  • 您对这两个独立的概念是正确的,但::operator new 在标准中被称为分配函数,而不是运算符。所以项目符号 #1 应该说“operator new()operator delete() 分配和释放函数。
  • 你的最后一行不是::operator delete(p); ,因为这是分配内存的地方吗?
【解决方案3】:

假设没有 Buffer::operator delete 这样的东西,delete buf; 版本是正确的,并且会进行所有适当的清理。为了更安全,你可以说::delete buf;

语言-律师辩论材料如下。

5.3.5/1

delete-expression 操作符销毁由 new-expression 创建的最派生对象 (1.8) 或数组。

删除表达式:

  • ::optdeletecast-expression
  • ::optdelete [ ]cast-expression

第一种选择是针对非数组对象,第二种是针对数组。 ...

5.3.5/2

...在第一种选择(delete object)中,delete 的操作数的值可能是一个空指针,一个指向由前一个 new-expression,或指向表示此类对象的基类的子对象(1.8)的指针(第 10 条)。如果不是,则行为未定义。

所以指针必须指向一个由new-expression创建的对象,它的定义是:

5.3.4/1

新表达式:

  • ::optnewnew-placementoptnew -type-id new-initializeropt
  • ::optnewnew-placementopt(type-id ) new-initializeropt

新位置:

  • ( 表达式列表 )

所以“新位置”确实算作一个新表达式。没有什么可以禁止 delete-expression 那里。

此外,尽管自定义创建,delete-expression 确实正确地清理了对象。

5.3.5/6-9

如果 delete-expression 的操作数的值不是空指针值,则 delete-expression 将调用对象的析构函数(如果有)或被删除的数组元素。 ...

如果delete-expression的操作数的值不是空指针值,delete-expression会调用一个deallocation函数 (3.7.4.2)。否则,未指定是否将调用释放函数。 [注意: 无论对象的析构函数还是数组的某个元素是否抛出异常,都会调用释放函数。 - 尾注]

delete-expression 中的关键字 delete 前面带有一元 :: 运算符时,全局释放函数用于释放存储空间。

所以::delete buf; 完全等价于:

try {
    buf->~Buffer();
} catch(...) {
    ::operator delete(mem);
    throw;
}
::operator delete(mem);

【讨论】:

  • 按照您的推理(placement new 是一个 new 表达式,因此我们可以使用 delete),这意味着您即使mem实际上被分配了malloc或其他东西,也可以使用delete buf
  • @zakinster:只有当被表达式调用的operator delete在分配方法下是明确定义的。
  • 在尝试调用析构函数后不应该还有删除吗?
猜你喜欢
  • 2016-07-27
  • 2018-07-10
  • 2013-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-07
  • 1970-01-01
相关资源
最近更新 更多