C++展示位置新的工作方式

How C++ placement new works?

本文关键字:工作 方式 位置 C++      更新时间:2023-10-16

这个问题是为了确认我正确理解了这个概念,并就用法风格和可能的优化征求专家意见。

我试图理解"安置新",以下是我想出的程序......

 #include <iostream>
 #include <new>
 class A {
 int *_a;
 public:
 A(int v) {std::cout<<"A c'tor clalledn";_a= new int(v);}
 ~A() {std::cout<<"A d'tor clalledn"; delete(_a);}
 void testFunction() {std::cout<<"I am a test function &_a = "<<_a<<" a = "<<*_a<<"n";}
};
int main()
{
    A *obj1 = new A(21);
    std::cout<<"Object allocated at "<<obj1<<std::endl;
    obj1->~A();
    std::cout<<"Object allocated at "<<obj1<<std::endl;
    obj1->testFunction();
    A *obj2 = new(obj1) A(22);
    obj1->testFunction();
    obj2->testFunction();
    delete(obj1);// Is it really needed now? Here it will delete both objects.. so this is not the right place.
    //obj1->testFunction();
    //obj2->testFunction();
    return 0;
}

当我运行这个程序时,我得到以下 o/p

A c'tor clalled
Object allocated at 0x7f83eb404c30
A d'tor clalled
Object allocated at 0x7f83eb404c30
I am a test function &_a = 0x7f83eb404c40 a = 21
A c'tor clalled
I am a test function &_a = 0x7f83eb404c40 a = 22
I am a test function &_a = 0x7f83eb404c40 a = 22
A d'tor clalled
I am a test function &_a = 0x7f83eb404c40 a = 0
I am a test function &_a = 0x7f83eb404c40 a = 0

我有以下问题...

  • 演示放置新位置的正确示例吗?
  • 成员 A 是动态分配的(没有新放置)。 那么为什么它为 obj1 和 obj2 获取相同的地址。这只是巧合吗?
  • 15号线的D'tor调用是一种好习惯吗?

也请指出你看到任何我可以改进或不尝试的东西。也欢迎任何好的参考或阅读。

这真的非常简单:new可以被认为是做两件事:

  1. 分配内存。
  2. 在分配的内存中放置构造对象。

不能保证实现实际使用malloc,但通常是这样。你不能假设它关于实现,但为了理解它是一个OK的假设。

因此,以下内容被认为是等效的:

auto obj1 = new std::string("1");
// ↑ can be thought of as equivalent to ↓ 
auto obj2 = (std::string*)malloc(sizeof(std::string));
new(obj2) std::string("2");

delete也是如此:

delete obj1;
// ↑ can be thought of as equivalent to ↓ 
obj2->~string();
free(obj2);

然后,当您看到newdelete它们的真实情况时,您可以轻松地推理出这一切:分配后跟构造函数调用,析构函数调用后跟释放分配。

当您使用放置new时,您决定单独处理第一步。内存仍然必须以某种方式分配,您只需完全控制它是如何发生的以及内存来自哪里。

因此,您必须分别跟踪两件事:

  1. 内存的生存期。

  2. 对象的生存期。

下面的代码演示了它们如何相互独立:

#include <cstdlib>
#include <string>
#include <new>
using std::string;
int main() {
    auto obj = (string*)malloc(sizeof(string));  // memory is allocated
    new(obj) string("1");  // string("1") is constructed
    obj->~string ();       // string("1") is destructed
    new(obj) string("2");  // string("2") is constructed
    obj->~string ();       // string("2") is destructed
    free(obj);             // memory is deallocated
}

如果对象的生存期超过内存的生存期,则程序具有 UB。确保内存始终超过对象的寿命。例如,这有 UB:

void ub() {
    alignas(string) char buf[sizeof(string)]; // memory is allocated
    new(buf) string("1");                     // string("1") is constructed
} // memory is deallocated but string("1") outlives the memory!

但这没关系:

void ub() {
    alignas(string) char buf[sizeof(string)]; // memory is allocated
    new(buf) string("1");                     // string("1") is constructed
    buf->~string();                           // string("1") is destructed
}                                             // memory is deallocated

请注意您需要如何使用 alignas 正确对齐自动缓冲区。缺少任意类型的alignas会导致 UB。它可能看起来有效,但这只会误导您。

在某些特定类型中,不调用析构函数和未正确对齐内存不会导致 UB,但您永远不应该假设某个类型有这样的事情。调用析构函数并进行对齐,如果事实证明没有必要,则不会花费您任何费用 - 不会为此类类型生成额外的代码。

struct S {
  char str[10];
}
这可能是

CodeReview.SE 的事情,在回答你的问题之前,让我稍微评论一下你的源代码。

A *obj1 = new A(21);
std::cout<<"Object allocated at "<<obj1<<std::endl;
obj1->~A();

通常,您永远不会在使用 placement-new 创建的对象上调用析构函数。在您的情况下,您破坏旧的并构建一个新的放置新。即使这有效,您也应该实现一些重置功能来重置对象,而不是破坏和构造一个新对象。

17    obj1->testFunction();

这是 UB。您已经破坏了该对象,不应对其调用任何方法。

18    A *obj2 = new(obj1) A(22);
19    obj1->testFunction();
20    obj2->testFunction();

没关系,请注意,obj1obj2是完全相同的对象。

21    delete(obj1);// Is it really needed now? Here it will delete both objects.. so this is not the right place.

你的评论是错误的。您不是在删除两个对象,而是在稍后删除一个对象。

22    obj1->testFunction();
23    obj2->testFunction();

这是 - 再次 - UB,不要在解构或删除的对象上调用方法。对于您的问题:

成员_a是动态分配的(没有新放置)。那么为什么它为 obj1 和 obj2 获得相同的地址。这只是巧合吗?

不要称它们为obj1obj2,因为这两个变量指向同一个对象,但是是的,这是巧合。在第一个对象被破坏并释放此内存后,第二个对象分配了刚刚释放的相同数量的内存,分配器决定为您提供完全相同的内存。

15号线的D'tor调用是一种好习惯吗?

不,不是。很少有例子为什么你需要调用析构函数,其中之一是你的对象是由 placement-new 创建的。在您的示例中,这没有副作用,因为您在解构旧对象后在同一位置构造一个新对象,并且新对象与旧对象的类型相同,否则这可能会以某种方式严重中断。

现在,请进一步了解删除后的评论。让我们看看new和新版位实际上做了什么。

一个新的做:

  • 从操作系统为新对象分配内存
  • 在新对象上调用构造函数,地址(this)设置为分配器获得的内存块。
删除操作会

起到相反的作用:

  • 调用对象的析构函数
  • 释放内存块
现在到放置-new

:放置-new只是跳过第一步(分配内存)并调用该新对象的构造函数,this设置为您传递的地址。因此,放置-new 的反面只是调用析构函数,因为不存在放置删除。

这意味着对于你的代码,在你调用析构函数之后,你的第一个对象死了,但你从未交还过内存,这就是为什么你可以在该内存中构造一个新对象。现在,当您调用 delete 时,第一个对象不再存在,只有它使用的内存,但是相同的内存现在被第二个对象阻塞,因此当您调用 delete 时,您不会删除两个对象,而是只删除第二个对象(您解构它,然后释放内存块)。

您可以在 isocpp 的常见问题解答中阅读有关主题放置-新内容以及何时调用析构函数的更多信息

C++展示位置新工作原理?

我试图理解"安置新",以下是我想出的程序......

这两个答案都很好。但是您也想知道它是如何工作的,因此,我将添加程序集的解释:

<小时 />
  • A *obj1 = new A(21);
<小时 />
call operator new(unsigned long)
mov esi, 21
mov rdi, rax
mov rbx, rax
call A::A(int)
<小时 />
  • A *obj2 = new(obj1) A(22);
<小时 />
  mov esi, 22
  mov rdi, rbx
  call A::A(int)
<小时 />

这就是它的工作原理,足够清楚,不需要更多解释,对吧?