C++放置新工作原理如何?

vik*_*ant 6 c++ placement-new

这个问题是为了确认我理解这个概念,并对使用方式和可能的优化方面采取专家意见.

我想了解"放置新",以下是我想出的程序......

 #include <iostream>
 #include <new>

 class A {
 int *_a;
 public:
 A(int v) {std::cout<<"A c'tor clalled\n";_a= new int(v);}
 ~A() {std::cout<<"A d'tor clalled\n"; 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;
}
Run Code Online (Sandbox Code Playgroud)

当我运行这个程序时,我得到了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
Run Code Online (Sandbox Code Playgroud)

我有以下问题......

  • 展示新的展示位置是一个正确的例子吗?
  • 成员a 是动态分配的(没有新的展示位置).那么为什么它为obj1和obj2获得相同的地址.这只是一个巧合吗?
  • 在第15行打电话是一个好习惯吗?

还要指出你看到的任何我可以改进的东西,或者只是不要尝试.任何好的参考或阅读也是受欢迎的.

Rei*_*ica 13

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

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

malloc实际上不能保证实际使用,但通常是这样.你无法假设它的实现,但为了理解它是一个好的假设.

因此,以下被认为是等价的:

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");
Run Code Online (Sandbox Code Playgroud)

同样适用于delete:

delete obj1;
// ? can be thought of as equivalent to ? 
obj2->~std::string();
free(obj2);
Run Code Online (Sandbox Code Playgroud)

然后,您可以轻松地推论这一切,当你看到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
}
Run Code Online (Sandbox Code Playgroud)

如果对象的生命周期超过内存的生命周期,则程序具有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!
Run Code Online (Sandbox Code Playgroud)

但这没关系:

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
Run Code Online (Sandbox Code Playgroud)

请注意您需要如何正确对齐自动缓冲区alignas.缺乏alignas任意类型会导致UB.它似乎可行,但这只会误导你.

在有些情况下不调用析构函数,而不是对准内存正常不会导致UB某些特定类型的,但你永远不应该承担关于类型的这样的事情.打电话给你的析构函数,做比对,如果事实证明是不必要的它不会花费你任何东西 - 没有多余的代码将这种类型的产生.

struct S {
  char str[10];
}
Run Code Online (Sandbox Code Playgroud)

  • 最好使用 `::operator new(sizeof(std::string))` 而不是 `malloc(sizeof(std::string))` 以获得实际的等效性。Dito `::operator delete(pointer)` 而不是 `free(pointer)`。 (2认同)

tka*_*usl 5

这可能是CodeReview.SE的内容,让我在回答您的问题之前对您的源代码进行评论.

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

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

17    obj1->testFunction();
Run Code Online (Sandbox Code Playgroud)

这是UB.你已经破坏了对象,你不应该在它上面调用任何方法.

18    A *obj2 = new(obj1) A(22);
19    obj1->testFunction();
20    obj2->testFunction();
Run Code Online (Sandbox Code Playgroud)

这是okayish,不过,请注意obj1,并obj2是完全相同的对象.

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

你的评论是错误的.你没有删除两个对象,你要删除一个,稍后再删除.

22    obj1->testFunction();
23    obj2->testFunction();
Run Code Online (Sandbox Code Playgroud)

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

成员_a是动态分配的(没有新的展示位置).那么为什么obj1和obj2获得相同的地址呢?这只是一个巧合吗?

不要调用它们obj1,obj2因为这两个变量指向同一个对象,但是,这是巧合.在第一个对象被破坏并释放了这个内存之后,第二个分配了相同数量的内存,它刚刚被释放,分配器决定给你完全相同的内存.

在第15行打电话是一个好习惯吗?

不,这不对.你需要调用析构函数的例子很少,其中一个就是你的对象是由placement-new创建的.在你的例子中,这没有副作用,因为你在解构旧对象之后在同一个地方构造一个新对象,而新对象与旧对象的类型相同,否则这可能会以某种方式破坏.

现在更多关于删除后的评论.让我们看看一个new和一个新的实际位置.

新的做法:

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

删除操作正好相反:

  • 调用对象的析构函数
  • 释放大块内存

现在到placement-new:placement-new只是跳过第一步(分配内存)并调用该对象的构造函数,并this设置为您传递的地址.因此,placement-new 的反义词只是调用析构函数,因为不存在placement-delete.

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

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