龙空技术网

C++|深入理解预定义重载操作符new(placement operator new)

小智雅汇 2703

前言:

此刻小伙伴们对“c语言用new”大致比较关心,朋友们都需要了解一些“c语言用new”的相关内容。那么小编同时在网上搜集了一些有关“c语言用new””的相关文章,希望你们能喜欢,姐妹们一起来了解一下吧!

对于动态内存管理,C语言的做法是使用库函数手动申请和释放动态内存空间,到了C++,新增了两个关键字(new、delete),另外,分别与[]结合,构成4上操作符(new、delete、new []、delete [])。为对象动态申请和使用动态内存的new、delete、不仅有相关的内存操作,还会分别调用构造函数和析构函数。

Complex* pc = new Complex(1,2);

相当于:

Complex *pc;void* mem = operator new( sizeof(Complex) ); //分配內存pc = static_cast<Complex*>(mem); //轉型pc->Complex::Complex(1,2); //構造函數

析构操作:

delete pc;

相当于:

Complex::~Complex(pc); // 析構函數operator delete(pc); // 釋放內存

new [] 需要有配对操作delete [],否则会出现动态内存泄漏:

对于C++,大部分操作符都可以重载,其中就包括了上述4个操作符。

A predefined overloaded instance of operator new is that of the placement operator new. It takes a second argument of type void*. The invocation looks as follows:

运算符new的预定义重载实例是placement operator new的预定义重载实例。它接受类型为void*的第二个参数。调用如下所示:

Point2w ptw = new ( arena ) Point2w;

(正如C语言除了malloc(),还有calloc()。)

where arena addresses a location in memory in which to place the new Point2w object. The implementation of this predefined placement operator new is almost embarrassingly trivial. It simply returns the address of the pointer passed to it:

其中arena在内存中寻址放置新Point2w对象的位置。这种预定义的placement operator new的实现几乎微不足道。它只返回传递给它的指针的地址:

void* operator new( size_t, void* p ){    return p;};

If all it does is return the address of its second argument, why use it at all? That is, why not simply write

如果它所做的只是返回第二个参数的地址,那么为什么还要使用它呢?也就是说,为什么不直接写呢

Point2w ptw = ( Point2w* ) arena;

since in effect that's what actually happens? Well, actually it's only half of what happens, and it's the second half that the programmer cannot explicitly reproduce. Consider these questions:

实际上,这就是实际发生的事情?实际上,这只是发生的事情的一半,这是程序员无法显式重现的第二部分。考虑以下问题:

1. What is the second half of the placement new operator expansion that makes it work (and that the explicit assignment of arena does not provide for)?

placement new operator 扩展的后半部分是什么使其工作(arena的显式分配没有提供)?

2. What is the actual type of the pointer represented by arena and what are the implications of that type?

arena表示的指针的实际类型是什么?该类型的含义是什么?

The second half of the placement new operator expansion is the automatic application of the Point2w constructor applied to the area addressed by arena:

placement new operator扩展的后半部分是应用于arena所处理区域的Point2w构造函数的自动应用:

// Pseudo C++ codePoint2w ptw = ( Point2w* ) arena;if ( ptw != 0 )    ptw->Point2w::Point2w();

This is what makes the use of the placement operator new so powerful. The instance determines where the object is to be placed; the compilation system guarantees that the object's constructor is applied to it.

这就是为什么placement operator new的使用如此强大。实例确定对象放置的位置;编译系统保证对其应用对象的构造函数。

There is one slight misbehavior, however. Do you see what it is? For example, consider this program fragment:

然而,有一个轻微的不当行为。你看到它是什么了吗?例如,考虑以下程序片段:

// let arena be globally definedvoid fooBar() {    Point2w *p2w = new ( arena ) Point2w;    // ... do it ...    // ... now manipulate a new object ...    p2w = new ( arena ) Point2w;}

If the placement operator constructs the new object "on top of" an existing object and the existing object has an associated destructor, the destructor is not being invoked. One way to invoke the destructor is to apply operator delete to the pointer. But in this case, that's the absolutely wrong thing to do:

如果placement operator在现有对象的“顶部”构造新对象,并且现有对象具有关联的析构函数,则不会调用析构函数。调用析构函数的一种方法是将运算符delete应用于指针。但在这种情况下,这样做是绝对错误的:

// not the right way to apply destructor heredelete p2w;  p2w = new ( arena ) Point2w;

The delete operator applies the destructor—this is what we want. But it also frees the memory addressed by p2w—this is definitely not what we want, since the next statement attempts to reuse it. Rather, we need to explicitly invoke the destructor and preserve the storage for reuse:

delete操作符应用析构函数这就是我们想要的。但它也释放了p2w寻址的内存,这肯定不是我们想要的,因为下一个语句试图重用它。相反,我们需要显式调用析构函数并保留存储以供重用:

Standard C++ has rectified this with a placement operator delete that applies the destructor to the object but does not free the memory. A direct call of the destructor is no longer necessary.

标准C++通过placement operator delete纠正了这一问题,该操作符将析构函数应用于对象,但不会释放内存。不再需要直接调用析构函数。

// the correct way of applying destructorp2w->~Point2w;p2w = new ( arena ) Point2w;

The only remaining problem is a design one: Does the first invocation of the placement operator in our example also construct the new object "on top of" an existing object, or is the arena addressed "raw"? That is, if we have

剩下的唯一问题是设计问题:在我们的示例中,placement operator的第一次调用是否也会在现有对象的“顶部”构造新对象,或者arena是否被寻址为“原始”?也就是说,如果我们有

Point2w *p2w = new ( arena ) Point2w;

how can we know whether the area addressed by arena needs first to be destructed? There is no language-supported solution to this. A reasonable convention is to have the site applying new be responsible for also applying the destructor.

我们怎么知道arena所涉及的区域是否需要首先被破坏?没有语言支持的解决方案。一个合理的惯例是让申请新的地址也负责申请析构函数。

The second question concerns the actual type of the pointer represented by arena. The Standard says it must address either a class of the same type or raw storage sufficient to contain an object of that type. Note that a derived class is explicitly not supported. For a derived class or otherwise unrelated type, the behavior, while not illegal, is undefined.

第二个问题涉及arena表示的指针的实际类型。该标准表示,它必须处理相同类型的类或足以包含该类型对象的原始存储。请注意,显式不支持派生类。对于派生类或其他不相关的类型,行为虽然不非法,但未定义。

Raw storage might be allocated as follows:

原始存储可以按以下方式分配:

char *arena = new char[ sizeof( Point2w ) ];

An object of the same type looks as you might expect:

相同类型的对象看起来与您预期的相同:

Point2w *arena = new Point2w;

In both cases, the storage for the new Point2w exactly overlays the storage location of arena, and the behavior is well defined. In general, however, the placement new operator does not support polymorphism. The pointer being passed to new is likely to address memory preallocated to a specific size. If the derived class is larger than its base class, such as in the following:

在这两种情况下,新Point2w的存储正好覆盖arena的存储位置,并且行为定义良好。然而,通常情况下,placement new operator不支持多态性。传递给new的指针可能会寻址预分配到特定大小的内存。如果派生类大于其基类,例如:

Point2w p2w = new ( arena ) Point3w;

application of the Point3w constructor is going to wreak havoc, as will most subsequent uses of p2w.

Point3w构造函数的应用将带来巨大的破坏,p2w的大多数后续使用也将如此。

One of the more "dark corner-ish" questions that arose when the new placement operator was introduced in Release 2.0 was the following example brought up by Jonathan Shopiro:

在2.0版中引入new placement operator时,出现了一个更为“黑暗角落”的问题,乔纳森·肖皮罗(Jonathan Shopiro)提出了以下示例:

struct Base { int j; virtual void f(); };struct Derived : Base { void f(); };void fooBar() {    Base b;    b.f(); // Base::f() invoked    b.~Base();    new ( &b ) Derived; // 1    b.f(); // which f() invoked?}

Since the two classes are the same size, the placement of the derived object within the area allocated for the base class is safe. Supporting it, however, would probably require giving up the general optimization of invoking statically all virtual functions calls through objects, such as b.f().

由于这两个类的大小相同,因此将派生对象放置在为基类分配的区域内是安全的。然而,支持它可能需要放弃通过对象(例如b.f())静态调用所有虚拟函数调用的一般优化。

Consequently, this use of the placement new operator is also unsupported under the Standard. The behavior of the program is undefined: We cannot say with certainty which instance of f() is invoked. (Most implementations, if they were to compile this, would probably invoke Base::f(), while most users would probably expect the invocation of Derived::f().)

因此,该标准也不支持使用placement new运算符。程序的行为尚未定义:我们无法确定调用了f()的哪个实例。(大多数实现如果要编译它,可能会调用Base::f(),而大多数用户可能希望调用Derived::f()。)

ref

Stanley B. Lippman 《Inside the C++ Object Model》

《cpp annotations》cplusplus09.html#l179

-End-

标签: #c语言用new