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

时间:2016-01-29 14:44:26

标签: 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;
}

当我运行这个程序时,我得到了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&amp;获得相同的地址呢? obj2的。这只是一个巧合吗?
  • 在第15行打电话是一个好习惯吗?

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

3 个答案:

答案 0 :(得分:11)

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

  1. 分配内存。
  2. 在已分配的内存中放置构造对象。
  3. 不能保证实现实际使用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->~std::string(); free(obj2);

    new

    当您看到deletenew的真实含义时,您可以轻松地推断出这一切:分配后跟构造函数调用,析构函数调用后跟释放。

    当您使用展示位置#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 } 时,您已决定单独处理第一步。内存必须以某种方式分配,你只需完全控制它的发生方式以及内存的来源。

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

    1. 记忆的生命周期。

    2. 对象的生命周期。

    3. 下面的代码演示了这些代码是如何彼此独立的:

      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!
      

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

      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

      请注意您需要使用SQL Code Template正确对齐自动缓冲区。对于任意类型缺少struct S { char str[10]; } 会导致UB。它似乎可行,但这只会误导你。

      有些特定的类型没有调用析构函数而没有正确对齐内存并不会导致UB,但是你不应该假设有关类型的东西。调用你的析构函数并进行对齐,如果事实证明它是不必要的,它将不会花费你任何东西 - 不会为这种类型生成额外的代码。

      {{1}}

答案 1 :(得分:5)

这可能适用于CodeReview.SE,让我在回答您的问题之前对您的源代码进行评论。

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

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

17    obj1->testFunction();

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

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

这是 okayish ,请注意,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&amp;获得相同的地址呢? obj2的。这只是巧合吗?

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

  

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

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

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

新的做法:

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

删除恰恰相反:

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

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

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

您可以在isocpp's faq

了解有关放置新主题以及何时调用析构函数的更多信息

答案 2 :(得分:0)

  

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

     

...

     

我正在努力理解&#34;放置新的&#34;以下是我提出的计划......

这两个答案都很棒。但是你也想知道它是如何工作的,所以,我将从汇编中添加解释:

  • 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)

它是如何运作的,足够清晰,不再需要解释,对吗?