这里有人用过c++的“placement new”吗?如果有,为什么?在我看来,它只在内存映射硬件上有用。


如果你正在构建一个内核,它很有用——你把从磁盘或页表读取的内核代码放在哪里?你得知道该往哪里跳。

或者在其他非常罕见的情况下,比如当你有大量分配的空间,想要把几个结构放在彼此后面。它们可以这样打包,而不需要使用offset()操作符。不过,还有其他的技巧。

我也相信一些STL实现使用了新位置,比如std::vector。它们以这种方式为2^n个元素分配空间,而不需要总是realloc。


我用它来存储带有内存映射文件的对象。 具体的例子是一个图像数据库,它处理大量的大图像(超过内存容量)。


如果你想把分配和初始化分开,这是很有用的。STL使用放置new来创建容器元素。


它被std::vector<>使用,因为std::vector<>通常分配比vector<>中的对象更多的内存。


放置new允许在已经分配的内存中构造一个对象。

当您需要构造一个对象的多个实例时,您可能需要这样做以进行优化,并且在每次需要新实例时不重新分配内存会更快。相反,对可以容纳多个对象的内存块执行一次分配可能会更有效,即使您不想一次使用所有内存块。

DevX给出了一个很好的例子:

标准c++也支持放置 New操作符,它构造 对象在预先分配的缓冲区上。这 在构建内存池时很有用, 垃圾收集器或简单的什么时候 性能和异常安全是重要的 派拉蒙(没有危险 内存分配失败 已经分配了,然后呢 对象上构造对象 预分配缓冲区占用的时间更少):

char *buf  = new char[sizeof(string)]; // pre-allocated buffer
string *p = new (buf) string("hi");    // placement new
string *q = new string("hi");          // ordinary heap allocation

您可能还希望确保在关键代码的特定部分(例如,在由起搏器执行的代码中)不存在分配失败。在这种情况下,您可能希望更早地分配内存,然后在临界区中使用placement new。

脱销在安置新

不应该释放正在使用内存缓冲区的每个对象。相反,您应该只删除[]原始缓冲区。然后必须手动调用类的析构函数。关于这方面的建议,请参阅Stroustrup的常见问题:是否存在“位置删除”?


我使用它来构造通过alloca()分配到堆栈上的对象。

无耻的宣传:我在这里写过博客。


我们将它用于自定义内存池。简单介绍一下:

class Pool {
public:
    Pool() { /* implementation details irrelevant */ };
    virtual ~Pool() { /* ditto */ };

    virtual void *allocate(size_t);
    virtual void deallocate(void *);

    static Pool *Pool::misc_pool() { return misc_pool_p; /* global MiscPool for general use */ }
};

class ClusterPool : public Pool { /* ... */ };
class FastPool : public Pool { /* ... */ };
class MapPool : public Pool { /* ... */ };
class MiscPool : public Pool { /* ... */ };

// elsewhere...

void *pnew_new(size_t size)
{
   return Pool::misc_pool()->allocate(size);
}

void *pnew_new(size_t size, Pool *pool_p)
{
   if (!pool_p) {
      return Pool::misc_pool()->allocate(size);
   }
   else {
      return pool_p->allocate(size);
   }
}

void pnew_delete(void *p)
{
   Pool *hp = Pool::find_pool(p);
   // note: if p == 0, then Pool::find_pool(p) will return 0.
   if (hp) {
      hp->deallocate(p);
   }
}

// elsewhere...

class Obj {
public:
   // misc ctors, dtors, etc.

   // just a sampling of new/del operators
   void *operator new(size_t s)             { return pnew_new(s); }
   void *operator new(size_t s, Pool *hp)   { return pnew_new(s, hp); }
   void operator delete(void *dp)           { pnew_delete(dp); }
   void operator delete(void *dp, Pool*)    { pnew_delete(dp); }

   void *operator new[](size_t s)           { return pnew_new(s); }
   void *operator new[](size_t s, Pool* hp) { return pnew_new(s, hp); }
   void operator delete[](void *dp)         { pnew_delete(dp); }
   void operator delete[](void *dp, Pool*)  { pnew_delete(dp); }
};

// elsewhere...

ClusterPool *cp = new ClusterPool(arg1, arg2, ...);

Obj *new_obj = new (cp) Obj(arg_a, arg_b, ...);

现在你可以将对象聚集在一个单独的内存区域中,选择一个非常快但不进行释放的分配器,使用内存映射,以及任何你希望通过选择池并将其作为参数传递给对象的放置new操作符来施加的语义。


我使用它来创建基于内存的对象,其中包含从网络接收到的消息。


一般来说,放置新是为了摆脱“正常新”的分配成本。

我使用它的另一个场景是,我想要访问一个仍待构造的对象的指针,以实现每个文档的单例。


我在实时编程中使用过它。我们通常不希望在系统启动后执行任何动态分配(或重新分配),因为无法保证这将花费多长时间。

我能做的是预先分配一大块内存(大到足以容纳类可能需要的任何数量)。然后,一旦我在运行时弄清楚如何构造这些东西,就可以在我想要的地方使用放置new来构造对象。我知道我使用它的一种情况是帮助创建异构循环缓冲区。

这当然不适合胆小的人,但这就是为什么他们把它的语法弄得有点粗糙。


The one place I've run across it is in containers which allocate a contiguous buffer and then fill it with objects as required. As mentioned, std::vector might do this, and I know some versions of MFC CArray and/or CList did this (because that's where I first ran across it). The buffer over-allocation method is a very useful optimization, and placement new is pretty much the only way to construct objects in that scenario. It is also used sometimes to construct objects in memory blocks allocated outside of your direct code.

我在类似的情况下使用过它,尽管它不经常出现。不过,它是c++工具箱中的一个有用工具。


我曾看到它被用作“动态类型”指针的轻微性能hack(在“引擎盖下”一节中):

但这是我用来获得小类型的快速性能的棘手技巧:如果所持有的值可以放入void*中,我实际上不需要分配一个新对象,而是使用placement new将其强制到指针本身。


脚本引擎可以在本机接口中使用它来从脚本分配本机对象。有关示例,请参阅Angelscript (www.angelcode.com/angelscript)。


Head Geek: BINGO! You got it totally - that's exactly what it's perfect for. In many embedded environments, external constraints and/or the overall use scenario forces the programmer to separate the allocation of an object from its initialization. Lumped together, C++ calls this "instantiation"; but whenever the constructor's action must be explicitly invoked WITHOUT dynamic or automatic allocation, placement new is the way to do it. It's also the perfect way to locate a global C++ object that is pinned to the address of a hardware component (memory-mapped I/O), or for any static object that, for whatever reason, must reside at a fixed address.


在http://xll.codeplex.com上查看xll项目中的fp.h文件,它解决了喜欢随身携带维度的数组“与编译器的不必要的亲密关系”问题。

typedef struct _FP
{
    unsigned short int rows;
    unsigned short int columns;
    double array[1];        /* Actually, array[rows][columns] */
} FP;

它可能在使用共享内存时很方便,在其他用途中…例如:http://www.boost.org/doc/libs/1_51_0/doc/html/interprocess/synchronization_mechanisms.html#interprocess.synchronization_mechanisms.conditions.conditions_anonymous_example


当您想重新初始化全局或静态分配的结构时,它也很有用。

旧的C方法是使用memset()将所有元素设置为0。在c++中,由于虚函数和自定义对象构造函数,无法做到这一点。

所以我有时会用下面的方法

 static Mystruct m;

 for(...)  {
     // re-initialize the structure. Note the use of placement new
     // and the extra parenthesis after Mystruct to force initialization.
     new (&m) Mystruct();

     // do-some work that modifies m's content.
 }

在序列化时(比如使用boost::serialization),放置new也非常有用。在c++的10年里,这只是我第二次需要新职位的情况(如果你包括面试的话,这是第三次:))。


我用它创建了一个Variant类(例如,一个对象可以表示一个单独的值,这个值可以是许多不同类型中的一个)。

如果Variant类支持的所有值类型都是POD类型(例如int, float, double, bool),那么带标签的C风格的联合就足够了,但如果你想要一些值类型是c++对象(例如std::string), C的联合特性就不行,因为非POD数据类型可能不会被声明为联合的一部分。

因此,我分配了一个足够大的字节数组(例如sizeof(the_largest_data_type_I_support)),并使用placement new在该区域初始化适当的c++对象,当Variant被设置为持有该类型的值时。(当然,当切换到不同的数据类型时,我事先手动调用对象的析构函数)


我认为任何答案都没有强调这一点,但新位置的另一个好例子和用法是减少内存碎片(通过使用内存池)。这在嵌入式和高可用性系统中特别有用。在最后一种情况下,这一点特别重要,因为对于一个必须运行24/365天的系统来说,没有碎片是非常重要的。此问题与内存泄漏无关。

Even when a very good malloc implementation is used (or similar memory management function) it's very difficult to deal with fragmentation for a long time. At some point if you don't manage cleverly the memory reservation/release calls you could end up with a lot of small gaps that are difficult to reuse (assign to new reservations). So, one of the solutions that are used in this case is to use a memory pool to allocate before hand the memory for the application objects. After-wards each time you need memory for some object you just use the new placement to create a new object on the already reserved memory.

这样,一旦应用程序启动,就已经预留了所需的所有内存。所有新的内存预留/释放都将分配到已分配的池中(您可能有几个池,每个池对应一个不同的对象类)。在这种情况下不会发生内存碎片,因为没有间隙,您的系统可以运行很长时间(数年)而不会出现内存碎片。

我在实践中看到过这种情况,特别是在VxWorks RTOS中,因为它的默认内存分配系统受到了很多碎片的影响。因此,通过标准的new/malloc方法分配内存在项目中基本上是被禁止的。所有的内存预留都应该到一个专用的内存池中。


实际上,实现任何类型的数据结构都需要分配比插入元素数量的最低要求更多的内存(即,除了每次分配一个节点的链接结构之外的任何数据结构)。

以unordered_map、vector或deque等容器为例。这些都为您插入的元素分配了比最低要求更多的内存,以避免为每次插入都需要堆分配。让我们用向量作为最简单的例子。

当你这样做时:

vector<Foo> vec;

// Allocate memory for a thousand Foos:
vec.reserve(1000);

... 其实也造不出一千个foo。它只是为它们分配/保留内存。如果vector没有在这里使用放置new,它将在所有地方默认构造foo,并且必须调用它们的析构函数,即使是对于你从未在第一个位置插入的元素。

分配=建设,解放=毁灭

一般来说,要实现像上面这样的许多数据结构,不能将分配内存和构造元素视为一个不可分割的事情,同样也不能将释放内存和销毁元素视为一个不可分割的事情。

为了避免不必要地向左或向右调用多余的构造函数和析构函数,这就是标准库将std::allocator(在分配/释放内存*时不构造或销毁元素)的思想与使用它的容器分开的原因,这些容器使用放置new手动构造元素,使用显式调用析构函数手动销毁元素。

我讨厌std::allocator的设计,但这是一个不同的主题,我将避免咆哮。: - d

So anyway, I tend to use it a lot since I've written a number of general-purpose standard-compliant C++ containers that could not be built in terms of the existing ones. Included among them is a small vector implementation I built a couple decades ago to avoid heap allocations in common cases, and a memory-efficient trie (doesn't allocate one node at a time). In both cases I couldn't really implement them using the existing containers, and so I had to use placement new to avoid superfluously invoking constructors and destructors on things unnecessary left and right.

当然,如果你曾经使用自定义分配器来单独分配对象,比如一个free list,那么你通常也会想使用placement new,就像这样(基本的例子,不需要担心异常安全或RAII):

Foo* foo = new(free_list.allocate()) Foo(...);
...
foo->~Foo();
free_list.free(foo);

这里是c++ in-place构造函数的杀手级用法:对齐缓存线,以及其他2边界的幂。以下是我的超快速指针对齐算法,使用5个或更少的单周期指令,达到2边界的任意幂:

/* Quickly aligns the given pointer to a power of two boundary IN BYTES.
@return An aligned pointer of typename T.
@brief Algorithm is a 2's compliment trick that works by masking off
the desired number in 2's compliment and adding them to the
pointer.
@param pointer The pointer to align.
@param boundary_byte_count The boundary byte count that must be an even
power of 2.
@warning Function does not check if the boundary is a power of 2! */
template <typename T = char>
inline T* AlignUp(void* pointer, uintptr_t boundary_byte_count) {
  uintptr_t value = reinterpret_cast<uintptr_t>(pointer);
  value += (((~value) + 1) & (boundary_byte_count - 1));
  return reinterpret_cast<T*>(value);
}

struct Foo { Foo () {} };
char buffer[sizeof (Foo) + 64];
Foo* foo = new (AlignUp<Foo> (buffer, 64)) Foo ();

这是不是让你的脸上露出了微笑(:)。我♥♥♥c++ 1x


这里有人用过c++的“placement new”吗?如果有,为什么?在我看来,它只在内存映射硬件上有用。

当需要复制(作为输出传递)时,它非常有用:

不可复制的对象(例如:operator=()被自动删除,因为该类包含一个const成员)或 不可平凡复制的对象(其中使用memcpy()是未定义的行为)

在一个函数中。

这(从函数中获得这些不可复制或不可简单复制的对象)可以帮助单元测试该函数,允许您看到某个数据对象在被该函数处理后现在看起来是某种方式,或者它可以只是常规API的一部分,用于您认为合适的任何用途。让我们通过这些例子来详细解释我的意思以及如何使用“新定位”来解决这些问题。

TLDR;

注意:我已经测试了这个答案中的每一行代码。它的工作原理。它是有效的。它不违反c++标准。

新位置为:

在c++中,当operator=()(赋值操作符)被删除时,for =的替换,你需要“复制”(实际上是复制-构造)一个因此不可复制的对象到给定的内存位置。 在c++中,当你的对象不可简单复制时,memcpy()的替换,这意味着使用memcpy()来复制这个不可简单复制的对象“可能是未定义的”。

Important: a "non-copyable" object is NOT truly non-copyable. It is simply not copyable via the = operator is all, which is a call to a class's underlying operator=() overload function. This means that when you do B = C;, what is actually happening is a call to B.operator=(C);, and when you do A = B = C;, what is actually happening is A.operator=(B.operator=(C));. Therefore, "non-copyable" objects are only copyable via other means, such as via the class's copy constructor, since, again, the class has no operator=() method. "Placement new" can be used to call any one of the many constructors which may exist in a class in order to construct an object into a desired pre-allocated memory location. Since "placement new" syntax allows for calling any constructor in a class, this includes passing it an existing instance of a class in order to have placement new call a class's copy-constructor to copy-construct a new object from the passed-in object into another place in memory. Copy-constructing one object into another place in memory...is a copy. This action creates a copy of that original object. When done, you CAN have two objects (instances), that are byte-identical, literally byte for byte (depending on your copy constructor's implementation), located in two separate places in memory. That, by definition, is a copy. It just wasn't done using the class's operator=() method is all.

因此,如果一个类没有operator=()方法,就可以将它定义为“不可复制的”,但它仍然是非常可复制的,合法的,根据c++标准和c++提供的机制,使用它的复制构造函数和放置新语法,安全且没有未定义的行为,如下所示。

提醒:下面的所有代码行都可以工作。您可以在这里运行大部分代码,包括下面的许多代码块,尽管它可能需要一些注释/取消注释代码块,因为它没有清晰地设置为单独的示例。

1. 什么是不可复制对象?

不可复制的对象不能使用=操作符(operator=()函数)进行复制。就是这样!然而,它仍然可以被合法复制。请看上面真正重要的注释。

不可复制类例1:

在这里,复制构造是可以的,但是禁止复制,因为我们已经显式删除了赋值操作符。试着做nc2 = nc1;导致编译时错误:

错误:使用已删除的函数' NonCopyable1& NonCopyable1::operator=(const NonCopyable1&) '

下面是完整的例子:

#include <stdio.h>

class NonCopyable1
{
public:
    int i = 5;

    // Delete the assignment operator to make this class non-copyable 
    NonCopyable1& operator=(const NonCopyable1& other) = delete;
};

int main()
{
    printf("Hello World\n");
    
    NonCopyable1 nc1;
    NonCopyable1 nc2;
    nc2 = nc1;   // copy assignment; compile-time error!
    NonCopyable1 nc3 = nc1; // copy constructor; works fine!

    return 0;
}

不可复制类例2:

在这里,复制构造是可以的,但是禁止复制,因为该类包含一个不能写入的const成员(假设是这样,因为显然有替代方法)。试着做nc2 = nc1;导致编译时错误:

错误:使用已删除的函数' NonCopyable1& NonCopyable1::operator=(const NonCopyable1&) ' 注意:' NonCopyable1& NonCopyable1::operator=(const NonCopyable1&) '被隐式删除,因为默认定义是格式错误的: const int NonCopyable1::i,不能使用默认的赋值操作符

完整的例子:

#include <stdio.h>

class NonCopyable1
{
public:
    const int i = 5; // classes with `const` members are non-copyable by default
};

int main()
{
    printf("Hello World\n");
    
    NonCopyable1 nc1;
    NonCopyable1 nc2;
    nc2 = nc1;   // copy assignment; compile-time error!
    NonCopyable1 nc3 = nc1; // copy constructor; works fine!

    return 0;
}

所以,如果一个类是不可复制的,你就不能通过下面的方法获取它的副本作为输出!line outputData = data;将导致编译失败,出现上面最后一个示例中所示的错误消息!

#include <functional>

#include <stdio.h>

class NonCopyable1
{
public:
    const int i; // classes with `const` members are non-copyable by default

    // Constructor to custom-initialize `i`
    NonCopyable1(int val = 5) : i(val) 
    {
        // nothing else to do 
    }
};

// Some class which (perhaps asynchronously) processes data. You attach a 
// callback, which gets called later. 
// - Also, this may be a shared library over which you have no or little 
// control, so you cannot easily change the prototype of the callable/callback 
// function. 
class ProcessData
{
public:
    void attachCallback(std::function<void(void)> callable)
    {
        callback_ = callable;
    }
    
    void callCallback()
    {
        callback_();
    }

private:
    std::function<void(void)> callback_;
};

int main()
{
    printf("Hello World\n");
    
    NonCopyable1 outputData; // we need to receive back data through this object
    printf("outputData.i (before) = %i\n", outputData.i); // is 5
    
    ProcessData processData;
    // Attach a lambda function as a callback, capturing `outputData` by 
    // reference so we can receive back the data from inside the callback via 
    // this object even though the callable prototype returns `void` (is a 
    // `void(void)` callable/function).
    processData.attachCallback([&outputData]()
        {
            int someRandomData = 999;
            NonCopyable1 data(someRandomData);
            // NOT ALLOWED SINCE COPY OPERATOR (Assignment operator) WAS 
            // AUTO-DELETED since the class has a `const` data member!
            outputData = data; 
        });
    processData.callCallback();
    // verify we get 999 here, NOT 5!
    printf("outputData.i (after) = %i\n", outputData.i); 

    return 0;
}

一个解决方案:memcpy数据到outputData。这在C中是完全可以接受的,但在c++中并不总是可以。

Cppreference.com状态(强调添加):

如果对象可能是重叠的或不是TriviallyCopyable的,则memcpy的行为没有指定,可能是未定义的。

and:

笔记 普通可复制类型的对象是唯一可以使用std::memcpy安全地复制的c++对象,或者使用std::ofstream::write()/std::ifstream::read()安全地序列化到二进制文件的c++对象。

(https://en.cppreference.com/w/cpp/string/byte/memcpy)

因此,为了安全起见,在使用memcpy()复制对象之前,我们要确保对象是普通可复制的。替换上面的部分:

    processData.attachCallback([&outputData]()
        {
            int someRandomData = 999;
            NonCopyable1 data(someRandomData);
            // NOT ALLOWED SINCE COPY OPERATOR (Assignment operator) WAS 
            // AUTO-DELETED since the class has a `const` data member!
            outputData = data; 
        });

用这个。注意,这次使用memcpy()来复制数据,使用std::is_trivially_copyable来确保在编译时,使用memcpy()复制这种类型确实是安全的!:

    // (added to top)
    #include <cstring>  // for `memcpy()`
    #include <type_traits> // for `std::is_trivially_copyable<>()`

    // Attach a lambda function as a callback, capturing `outputData` by 
    // reference so we can receive back the data from inside the callback via 
    // this object even though the callable prototype returns `void` (is a 
    // `void(void)` callable/function).
    processData.attachCallback([&outputData]()
        {
            int someRandomData = 999;
            NonCopyable1 data(someRandomData);
            static_assert(std::is_trivially_copyable<NonCopyable1>::value, "NonCopyable1 must "
                "be a trivially-copyable type in order to guarantee that `memcpy()` is safe "
                "to use on it.");
            memcpy(&outputData, &data, sizeof(data));
        });

示例程序输出,现在它可以编译和运行。它的工作原理!

你好世界 outputData。I (before) = 5 outputData。I (after) = 999

然而,为了更加安全,你应该在覆盖对象之前手动调用它的析构函数,如下所示:

最佳memcpy()解决方案:

    processData.attachCallback([&outputData]()
        {
            int someRandomData = 999;
            NonCopyable1 data(someRandomData);
            static_assert(std::is_trivially_copyable<NonCopyable1>::value, "NonCopyable1 must "
                "be a trivially-copyable type in order to guarantee that `memcpy()` is safe "
                "to use on it.");
            outputData.~NonCopyable1(); // manually call destructor before overwriting this object
            memcpy(&outputData, &data, sizeof(data));
        });

但是,如果上面的static_assert()失败了,那么就不应该使用memcpy()。因此,一个始终安全且更好的c++替代方案是使用“placement new”。

Here, we simply copy-construct data right into the memory region occupied by outputData. That's what this "placement new" syntax does for us! It does NOT dynamically allocate memory, like the new operator normally does. Normally, the new operator first dynamically allocates memory on the heap and then constructs an object into that memory by calling the object's constructor. However, placement new does NOT do the allocation part. Instead, it simply skips that part and constructs an object into memory at an address you specify! YOU have to be the one to allocate that memory, either statically or dynamically, beforehand, and YOU have to ensure that memory is properly aligned for that object (see alignof and alignas and the Placement new example here) (it will be in this case since we explicitly created the outputData object as an object, calling it's constructor with NonCopyable1 outputData;), and YOU have to ensure that the memory buffer/pool is large enough to hold the data you are about to construct into it.

泛型放置的新语法是这样的

// Call`T`'s specified constructor below, constructing it as an object right into
// the memory location pointed to by `ptr_to_buffer`. No dynamic memory allocation
// whatsoever happens at this time. The object `T` is simply constructed into this
// address in memory.
T* ptr_to_T = new(ptr_to_buffer) T(optional_input_args_to_T's_constructor);

在我们的例子中,它看起来像这样,调用NonCopyable1类的复制构造函数,我们已经在上面反复证明了即使删除赋值/复制操作符也有效:

// copy-construct `data` right into the address at `&outputData`, using placement new syntax
new(&outputData) NonCopyable1(data); 

我们最终的attachCallback lambda现在看起来像这样,在memcpy()的位置使用了新语法。注意,确保对象可普通复制的检查不再需要。

===>最好的c++解决方案全方位-避免memcpy通过复制构造直接到目标内存位置使用放置new: <====使用此!= = = =

    processData.attachCallback([&outputData]()
        {
            int someRandomData = 999;
            NonCopyable1 data(someRandomData);
            outputData.~NonCopyable1(); // manually call destructor before overwriting this object
            // copy-construct `data` right into the address at `&outputData`, using placement new syntax
            new(&outputData) NonCopyable1(data); 

            // Assume that `data` will be further manipulated and used below now, but we needed
            // its state at this moment in time. 

            // Note also that under the most trivial of cases, we could have also just called
            // out custom constructor right here too, like this. You can call whatever
            // constructor you want!
            // new(&outputData) NonCopyable1(999);

            // ...
        });

2. 什么是非平凡可复制对象?

A non-trivially-copyable object may be one which contains virtual methods and things, as this can lead to the class having to track "vee pointers" (vptr) and "vee tables" (vtbls), to point to the proper virtual implementation in memory. Read more about that here: Dr. Dobb's "Storage Layout of Polymorphic Objects". However, even in this case, so long as you're memcpy()ing from the same process to the same process (ie: within the same virtual memory space), and NOT between processes, and NOT deserializing from disk to RAM, it seems to me that memcpy() would technically work fine and produce no bugs (and I've proven this in a handful of examples to myself), but it technically seems to be behavior which is not defined by the C++ standard, so therefore it is undefined behavior, so therefore it can't be relied upon 100% from compiler to compiler, and from one version of C++ to the next, so...it is undefined behavior and you shouldn't memcpy() in that case.

换句话说,如果static_assert(std::is_trivially_copyable<NonCopyable1>::value);以上检查失败,不要使用memcpy()。你必须使用“放置新”代替!

让静态断言失败的一种方法是简单地在你的NonCopyable1类的类定义中声明或定义一个自定义的复制/赋值操作符,就像这样:

// Custom copy/assignment operator declaration:
NonCopyable1& operator=(const NonCopyable1& other);

// OR:

// Custom copy/assignment operator definition:
NonCopyable1& operator=(const NonCopyable1& other)
{
    // Check for, **and don't allow**, self assignment! 
    // ie: only copy the contents from the other object 
    // to this object if it is not the same object (ie: if it is not 
    // self-assignment)!
    if(this != &other) 
    {
        // copy all non-const members manually here, if the class had any; ex:
        // j = other.j;
        // k = other.k;
        // etc.
        // Do deep copy of data via any member **pointers**, if such members exist
    }

    // the assignment function (`operator=()`) expects you to return the 
    // contents of your own object (the left side), passed by reference, so 
    // that constructs such as `test1 = test2 = test3;` are valid!
    // See this reference, from Stanford, p11, here!:
    // http://web.stanford.edu/class/archive/cs/cs106b/cs106b.1084/cs106l/handouts/170_Copy_Constructor_Assignment_Operator.pdf
    //      MyClass one, two, three;
    //      three = two = one;
    return *this; 
}

(更多关于自定义复制构造函数、赋值操作符等的例子,以及“三原则”和“五原则”,请参阅这里的hello world存储库和示例。)

所以,现在我们有了一个自定义赋值操作符,类不再是普通的可复制的,下面的代码:

    processData.attachCallback([&outputData]()
        {
            int someRandomData = 999;
            NonCopyable1 data(someRandomData);
            static_assert(std::is_trivially_copyable<NonCopyable1>::value, "NonCopyable1 must "
                "be a trivially-copyable type in order to guarantee that `memcpy()` is safe "
                "to use on it.");
            outputData.~NonCopyable1(); // manually call destructor before overwriting this object
            memcpy(&outputData, &data, sizeof(data));
        });

将产生以下错误:

main.cpp: main.cpp:151:13: error: static断言失败:NonCopyable1必须是一个普通可复制的类型,以保证' memcpy() '对其使用是安全的。 static_assert(std::is_trivially_copyable<NonCopyable1>::value, "NonCopyable1必须" ^~~~~~~~~~~~~

所以,你必须/(真的应该)使用“placement new”来代替,就像上面所描述的那样:

    processData.attachCallback([&outputData]()
        {
            int someRandomData = 999;
            NonCopyable1 data(someRandomData);
            outputData.~NonCopyable1(); // manually call destructor before overwriting this object
            // copy-construct `data` right into the address at `&outputData`, using placement new syntax
            new(&outputData) NonCopyable1(data); 
        });

更多关于使用"placement new"预分配缓冲区/内存池的信息

如果你真的只是使用placement new来复制构造到内存池/共享内存/预分配的对象空间中,没有必要使用NonCopyable1 outputData;在内存中构造一个无用的实例,不管怎样我们都要销毁它。相反,您可以只使用字节的内存池。格式如下:

(摘自:“新植入”部分:https://en.cppreference.com/w/cpp/language/new)

// within any scope...
{
    char buf[sizeof(T)];  // Statically allocate memory large enough for any object of
                          // type `T`; it may be misaligned!
    // OR, to force proper alignment of your memory buffer for your object of type `T`, 
    // you may specify memory alignment with `alignas()` like this instead:
    alignas(alignof(T)) char buf[sizeof(T)];
    T* tptr = new(buf) T; // Construct a `T` object, placing it directly into your 
                          // pre-allocated storage at memory address `buf`.
    tptr->~T();           // You must **manually** call the object's destructor.
}                         // Leaving scope here auto-deallocates your statically-allocated 
                          // memory `buf`.

所以,在上面的例子中,这个静态分配的输出缓冲区:

// This constructs an actual object here, calling the `NonCopyable1` class's
// default constructor.
NonCopyable1 outputData; 

会变成这样:

// This is just a statically-allocated memory pool. No constructor is called.
// Statically allocate an output buffer properly aligned, and large enough,
// to store 1 single `NonCopyable1` object.
alignas(alignof(NonCopyable1)) uint8_t outputData[sizeof(NonCopyable1)];
NonCopyable1* outputDataPtr = (NonCopyable1*)(&outputData[0]);

然后通过outputDataPtr指针读取outputData对象的内容。

The former method (NonCopyable1 outputData;) is best if a constructor for this class exists which requires no input parameters you do NOT have access to at the time of the creation of this buffer, and if you only intend to store this one data type into this buffer, whereas the latter uint8_t buffer method is best if you either A) do NOT have access to all of the input parameters required to even construct the object at the location you need to create this buffer, OR B) if you plan to store multiple data types into this memory pool, perhaps for communicating between threads, modules, processes, etc, in a union-sort of way.

更多关于c++的内容,以及为什么它让我们在这种情况下跳过这些圈

So, this whole "placement new" thing in C++, and the need for it, took me a lot of study and a long time to wrap my mind around it. After thinking about it, it has occurred to me that the paradigm of C (where I come from) is to manually allocate some memory, then stick some stuff into it. These are intended to be separate actions when dealing with both static and dynamic memory allocation (remember: you can't even set default values for structs!). There is no concept of a constructor or destructor, and even getting the behavior of a scope-based destructor which gets automatically called as a variable exits a given scope is a pain-in-the-butt and requires some fancy gcc extension __attribute__((__cleanup__(my_variable))) magic as I demonstrate in my answer here. Arbitrarily copying from one object to another, however, is super easy. Just copy the objects around! This is contrasted to the paradigm of C++, which is RAII (Resource Acquisition is Initialization). This paradigm focuses on objects being ready for use the instant they are created. To accomplish this, they rely on constructors and destructors. This means that creating an object like this: NonCopyable1 data(someRandomData);, doesn't just allocate memory for that object, it also calls the object's constuctor and constructs (places) that object right into that memory. It tries to do multiple things in one. So, in C++, memcpy() and the assignment operator (=; AKA: operator=() function) are explicitly more limited by the nature of C++. This is why we have to go through the hoops of this weird "copy-construct my object into a given memory location via placement new" process in C++ instead of just creating a variable and copying stuff into it later, or memcpy()ing stuff into it later if it contains a const member, like we would do in C. C++ really tries to enforce RAII, and this is in part how they do it.

你可以使用std::optional<>::emplace()代替

从c++ 17开始,你也可以使用std::optional<>作为包装器。现代c++的各种容器和包装器的emplace()函数做的是我们上面手动做的“placement new”(也见我在这里的回答和关于std::vector<T,Allocator>::emplace_back“通常使用place -new来构造元素in-place”的引用)。

optional静态地为你想要放入的对象分配一个足够大的缓冲区。然后,它要么存储该对象,要么存储std::nullopt(与{}相同),这意味着它不存储该对象。要用另一个对象替换其中的一个对象,只需调用std::可选对象上的emplace()方法。它的作用如下:

就地构造包含的值。如果*this在调用之前已经包含一个值,则通过调用其析构函数来销毁所包含的值。

因此,它首先手动调用已经在它内部的现有对象的析构函数,如果现有对象已经在它内部,那么它将执行相当于“放置new”的操作,将一个新对象(由您提供)复制-构造到该内存空间中。

那么,这个输出缓冲区:

NonCopyable1 outputData; 

// OR

alignas(alignof(NonCopyable1)) uint8_t outputData[sizeof(NonCopyable1)];
NonCopyable1* outputDataPtr = (NonCopyable1*)(&outputData[0]);

现在变成这样:

# include <optional>

std::optional<NonCopyable1> outputData = std::nullopt;

这个“放置新的”拷贝构造到输出缓冲区中:

processData.attachCallback([&outputData]()
    {
        int someRandomData = 999;
        NonCopyable1 data(someRandomData);
        outputData.~NonCopyable1(); // manually call destructor before overwriting this object
        // copy-construct `data` right into the address at `&outputData`, using placement new syntax
        new(&outputData) NonCopyable1(data); 
    });

现在变成了这个emplace()将新数据放入缓冲区。注意,不再需要手动调用析构函数,因为std::optional<>::emplace()已经为我们处理了对任何已经存在的对象调用析构函数!:

processData.attachCallback([&outputData]()
    {
        int someRandomData = 999;
        NonCopyable1 data(someRandomData);
        // emplace `data` right into the `outputData` object
        outputData.emplace(data);
    });

现在,要从outputData中获取数据,只需使用*对其进行解引用,或对其调用.value()。例:

// verify we get 999 here!
if (outputData.has_value())
{
    printf("(*outputData).i (after) = %i\n", (*outputData).i);
    // OR 
    printf("outputData.value().i (after) = %i\n", outputData.value().i);
}
else 
{
    printf("outputData.has_value() is false!");
}

样例输出:

你好世界 (* outputData)。I (after) = 999 outputData.value()。I (after) = 999

在这里运行完整的示例代码。

参考文献和补充,优秀的阅读:

*****+[some of the most-useful and simplest "placement new" examples Iv'e ever seen!] https://www.geeksforgeeks.org/placement-new-operator-cpp/ [great example] https://en.cppreference.com/w/cpp/language/new --> see the "Placement new" section and example here! (I helped write the example). How do I make this C++ object non-copyable? [makes the really important point that calling the placement new line calls the object's constructor as it constructs it!: Line #3 (Fred* f = new(place) Fred();) essentially just calls the constructor Fred::Fred(). This means "the this pointer in the Fred constructor will be equal to place".] http://www.cs.technion.ac.il/users/yechiel/c++-faq/placement-new.html http://www.cs.technion.ac.il/users/yechiel/c++-faq/memory-pools.html Dr. Dobb's "Storage Layout of Polymorphic Objects" [good pre-C++11 intro to the C++ "Rule of Three"] http://web.stanford.edu/class/archive/cs/cs106b/cs106b.1084/cs106l/handouts/170_Copy_Constructor_Assignment_Operator.pdf My "hello world" example and repository, demoing custom copy constructors, assignment operators, etc., related to the C++ "Rule of Three" / "Rule of Five" / "Rule of Zero" / "Rule of 0/3/5": https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world/blob/master/cpp/copy_constructor_and_assignment_operator/copy_constructor_and_assignment_operator.cpp [an excellent writeup by Microsoft on the usage of C++17's std::optional<> type] https://devblogs.microsoft.com/cppblog/stdoptional-how-when-and-why/ [related, since "placement new" very clearly solves this problem too, as this problem was the crux of and driving force behind most of my solutions and examples here!] const member and assignment operator. How to avoid the undefined behavior?


我也有个主意。 c++确实有零开销原则。 但是异常不遵循这个原则,所以有时它们会被编译器开关关闭。

让我们来看看这个例子:

#include <new>
#include <cstdio>
#include <cstdlib>

int main() {
    struct A {
        A() {
            printf("A()\n");
        }
        ~A() {
            printf("~A()\n");
        }
        char data[1000000000000000000] = {}; // some very big number
    };

    try {
        A *result = new A();
        printf("new passed: %p\n", result);
        delete result;
    } catch (std::bad_alloc) {
        printf("new failed\n");
    }
}

我们在这里分配一个大的结构体,检查分配是否成功,然后删除它。

但是如果我们关闭了异常,我们就不能使用try block,并且无法处理new[]失败。

我们怎么做呢?以下是如何做到的:

#include <new>
#include <cstdio>
#include <cstdlib>

int main() {
    struct A {
        A() {
            printf("A()\n");
        }
        ~A() {
            printf("~A()\n");
        }
        char data[1000000000000000000] = {}; // some very big number
    };

    void *buf = malloc(sizeof(A));
    if (buf != nullptr) {
        A *result = new(buf) A();
        printf("new passed: %p\n", result);
        result->~A();
        free(result);
    } else {
        printf("new failed\n");
    }
}

使用简单的malloc 检查是否是C方式失败 如果成功了,我们就使用新位置 手动调用析构函数(不能直接调用delete) 电话免费,由于我们叫malloc

UPD @Useless写了一个注释,它向我的视图打开了new(nothrow)的存在,在这种情况下应该使用它,但不是我之前写的方法。请不要使用我之前写的代码。对不起。


我还有一个想法(它对c++ 11有效)。

让我们看看下面的例子:

#include <cstddef>
#include <cstdio>

int main() {
    struct alignas(0x1000) A {
        char data[0x1000];
    };

    printf("max_align_t: %zu\n", alignof(max_align_t));

    A a;
    printf("a: %p\n", &a);

    A *ptr = new A;
    printf("ptr: %p\n", ptr);
    delete ptr;
}

使用c++ 11标准,GCC给出以下输出:

max_align_t: 16
a: 0x7ffd45e6f000
ptr: 0x1fe3ec0

PTR没有正确对齐。

对于c++ 17标准和更高级的标准,GCC给出了以下输出:

max_align_t: 16
a: 0x7ffc924f6000
ptr: 0x9f6000

PTR对齐正确。

据我所知,c++标准在c++ 17之前不支持过对齐的new,如果你的结构的对齐大于max_align_t,你就会遇到问题。 要在c++ 11中绕过这个问题,可以使用aligned_alloc。

#include <cstddef>
#include <cstdlib>
#include <cstdio>
#include <new>

int main() {
    struct alignas(0x1000) A {
        char data[0x1000];
    };

    printf("max_align_t: %zu\n", alignof(max_align_t));

    A a;
    printf("a: %p\n", &a);

    void *buf = aligned_alloc(alignof(A), sizeof(A));
    if (buf == nullptr) {
        printf("aligned_alloc() failed\n");
        exit(1);
    }
    A *ptr = new(buf) A();
    printf("ptr: %p\n", ptr);
    ptr->~A();
    free(ptr);
}

在这种情况下PTR是对齐的。

max_align_t: 16
a: 0x7ffe56b57000
ptr: 0x2416000