【问题标题】:Use of : Construction of objects at predetermined location in C++用途:在 C++ 中的预定位置构造对象
【发布时间】:2012-04-17 20:43:21
【问题描述】:

在 C++ 中在预定位置构造对象有什么用?

以下代码说明了在预定位置的构造-

void *address = (void *) 0xBAADCAFE ;
MyClass *ptr = new (address) MyClass (/*arguments to constructor*/) ;

这最终会在预定的“地址”处创建 MyClass 的对象。 (假设 address 指向的存储空间足够大,可以容纳 MyClass 对象。

我想知道在内存中这些预定位置创建对象的用途。

【问题讨论】:

  • 从未使用过这种分配方式,但我认为它用于实时系统中,预先分配内存并手动分区以加快分配速度
  • @giorashc- 今天我第一次遇到这样的分配!这也称为 "Placement new" 吗? (对我原来的帖子的修改建议对标题进行此更改)
  • 是的,这被称为Placement new
  • 另见this question的回答

标签: c++ constructor placement-new


【解决方案1】:

当您通过将 long、DWORD、DWORD_PTR 或其他大小的指针作为参数传递给函数并需要重建类的副本以供 OO 使用时知道类的地址时,此特定行为很有用。

另外,这也可以用于在预分配的内存或您确定为静态的位置中创建一个类(即:您正在将您的应用程序与一些古老的 ASM 库链接)。

【讨论】:

    【解决方案2】:

    通常您在嵌入式或驱动程序代码中使用预定位置,其中某些硬件通过特定地址范围寻址。

    但是在这种情况下,该地址的存储不用于访问,或者更好的是它不是预期的(或者更好的是不必用于它,因为您不知道新的操作员正在使用它),稍后在新的操作上执行。

    您将它用作初始化值(新并没有真正改变它)。 我想到了两个目的:首先,如果您稍后忘记了新的,您会立即在调试器中看到您的 magic 地址(即在本例中为 0xBAAADCAFE)。

    其次,你可以在你摆弄 new 运算符并需要一个 init 值的情况下使用它,这样你就可以调试它(例如,你可以看到更改)。

    或者你已经修改了你的新操作符,它可以用那个幻数来做任何事情(例如,你可以用它来调试,或者像上面提到的那样,确实为某些硬件使用特定地址的内存),在不同的分配方法,...

    编辑:要在这种情况下正确回答它,需要查看 new 运算符的真正作用,您应该检查该新闻源代码。

    【讨论】:

      【解决方案3】:

      自定义分配器、实时(此处没有锁定)和性能。

      【讨论】:

        【解决方案4】:

        主要有两种情况:

        第一种情况是——例如在嵌入式系统中——你必须在给定的众所周知的地方构造一个对象。

        第二个是当您出于某种原因想要以默认方式以外的方式管理内存时。

        在 C++ 中,像 pA = new(...) A(...) 这样的表达式会连续做两件事:

        1. 调用void* operator new(size_t, ...) 函数,随后调用
        2. 致电A::A(...)

        由于调用 new 是调用 A::A() 的唯一方法,因此向 new 添加参数允许专门化不同的方式来管理内存。最琐碎的就是“使用已经通过其他方式获得的内存”。

        当需要将分配和构造分开时,这种方法很好。典型的例子是std::allocator,其目的是为给定的数量分配未初始化的内存,而对象的构造发生在后面。

        例如,在std::vector 中会发生这种情况,因为它必须分配一个通常比其实际size 更宽的capacity,然后在已经存在push_back 的空间中构造对象存在。

        事实上,默认的 std::allocator 实现,当要求分配 n 个对象时,会执行 return reinterpret_cast<T*>(new char[n*sizeof(T)]),因此分配空间,但实际上不构造任何东西。

        承认 std::vector 存储:

        T* pT;  //the actual buffer
        size_t sz; //the actual size
        size_t cap; //the actual capacity
        allocator<T> alloc;
        

        push_back 的实现可以是:

        void vector<T>::push_back(const T& t)
        {
           if(sz==cap)
           {
               size_t ncap = cap + 1+ cap/2; //just something more than cap 
               T* npT = alloc.allocate(ncap); 
               for(size_t i=0; i<sz; ++i) 
               {
                  new(npT+i)T(pt[i]); //copy old values (may be move in C++11)
                  pt[i].~T(); // destroy old value, without deallocating
               }
               alloc.deallocate(pt,cap);
               pT = npT; 
               cap = ncap;
               // now we heve extra capacity       
           }
           new(pT+sz)T(t); //copy the new value
           ++sz; //actual size grown
        }
        

        本质上,需要将分配(与整个缓冲区相关)与元素的构造(必须发生在已经存在的缓冲区中)分开。

        【讨论】:

          【解决方案5】:

          placement new 有用的一个场景是:

          您可以预先分配大缓冲区一次,然后使用多个放置 new 运算符。
          这为您提供了更好的性能(您不需要每次都重新分配)和更少的碎片内存(当您需要小内存块时)。通常这是 std::vector 实现使用的内容。

          缺点是,您必须手动管理分配的内存。一旦不再需要,由放置new 分配的对象需要显式调用析构函数。

          鉴于始终建议分析您的应用程序的瓶颈,而不是跑到放置新位置进行预优化。

          【讨论】:

            猜你喜欢
            • 2019-01-18
            • 2017-02-22
            • 1970-01-01
            • 2010-10-25
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-09-26
            • 1970-01-01
            相关资源
            最近更新 更多