代码之家  ›  专栏  ›  技术社区  ›  vikrant

C++放置新的工作原理是什么?

  •  15
  • vikrant  · 技术社区  · 10 年前

    这个问题是为了确认我正确理解了这个概念,并对使用方式和可能的优化提出了专家意见。

    我正在努力理解“新安置”,下面是我想出的方案。。。

     #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
    

    我有以下问题。。。

    • 这是一个正确的例子来演示新的布局吗?
    • 成员 动态分配(没有新的放置)。那么,为什么obj1&这只是巧合吗?
    • 在15号线打电话是一种好做法吗?

    请同时指出,你看到了我可以改进的地方,或者只是没有尝试。任何好的参考或阅读也欢迎。

    3 回复  |  直到 8 年前
        1
  •  25
  •   Kuba hasn't forgotten Monica    6 年前

    这真的非常简单: 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);
    

    当你看到这一切的时候,你可以很容易地推理 删去 它们的真正含义是:分配后是构造函数调用,析构函数调用后是释放。

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

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

    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 .缺乏 对齐 对于任意类型,结果为UB。这看起来可能有效,但那只是误导你。

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

    struct S {
      char str[10];
    }
    
        2
  •  6
  •   Community Mohan Dere    9 年前

    这可能是为了 CodeReview.SE ,在我回答您的问题之前,让我先评论一下您的源代码。

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

    你通常不会调用对象的析构函数 使用新放置创建。在您的情况下,您将销毁旧的,并使用放置新的构造新的。尽管这是有效的,您还是应该实现一些 重置 重置对象而不是销毁和构造新对象的功能。

    17    obj1->testFunction();
    

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

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

    这是 奥卡伊语 ,但请注意 obj1 obj2 是完全相同的对象。

    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&这只是巧合吗?

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

    在15号线打电话是一种好做法吗?

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

    现在,在删除后,再详细了解一下您的评论。让我们看看 new 而一个新的位置确实如此。

    新产品:

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

    删除的效果相反:

    • 调用对象的析构函数
    • 取消分配内存块

    现在到placement new:placement新只跳过第一步(分配内存)并调用其构造函数 对象 设置为您传递的地址。这个 对面的 因此,placement new只是调用析构函数,因为不存在placement delete。

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

    您可以阅读有关主题放置的更多信息,以及何时调用析构函数 isocpp's faq

        3
  •  -1
  •   Community Mohan Dere    6 年前

    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)
    

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

    推荐文章