理解C++ placement语法

2014-04-13  本文已影响0人  一个淡定的程序员

最近小组读书活动让我对 placement new 和 placement delete 有了更加深入的理解.

关于new表达式

C++ 提供了new关键字和delete关键字, 分别用于申请和释放内存空间, 其中new表达式的语法如下:

new new-type-id ( optional-initializer-expression-list )

书上说, new表达式做两件事情

另外, 我了解到, 如果用户希望在自定义内存空间上构造对象, 可以调用另一个new表达式, 语法如下:

new ( expression-list ) new-type-id ( optional-initializer-expression-list )

具体使用起来, 就像这样:

void *buffer = malloc(sizeof(ClassA));
ClassA *ptr = new(buffer)ClassA();

第二种new表达式就像第一种的特例, 只需要完成步骤2, 而把步骤1留给用户自理.

关于delete表达式##

C++ 提供的delete表达式的语法:

delete type_pointer;

书上说, delete表示完成两件事情:

特别注意的是, 让type_pointer等于NULL, delete表达式也能正确功能, 但是对同一个指针, 重复调用delete将带来未定义错误

标准 operator new 和 operator delete##

C++ 标准的 new expression 内部调用的是标准的operator new(), 它的定义如下:

void * operator new (std::size_t) throw(std::bad_alloc);

operator new()是一个操作符或者函数, 它完成标准的allocation, 即内存分配.

类似的, C++标准的delete expression 内部调用的是标准的operator delete(), 它的定义如下:

void operator delete (void *) throw();

operator delete()也是一个操作符或者函数, 它完成标准的deallocation, 即内存释放

placement new 和 placement delete##

C++标准的 new 表达式能完成大部分的需求, 但不是全部, 比如: 如何在已有的内存空间上创建对象, 标准 new 表达式做不了, C++也不支持直接在raw内存上调用对象的构造函数, 于是placement new就产生了, 取名placement, 也说明了它的历史来源, 即在原地构建一个新对象.

当然原地创建对象只是一部分, placement new有更广大的外延, 而且placement new expression和placement operator new(), 通常都被笼统的成为placement new, 混淆了概念.

什么是placement operator new(), 它首先是一个函数, 而且是标准operator new()的一个重载函数. wiki中这么定义它:

The "placement" versions of the new and delete operators and functions are known as placement new and placement delete.

什么是placement new expression, 它属于C++语法, 类似于标准的new expression, 不同的是,内部调用的是相应的placement operator new(), wiki上这么定义它:

Any new expression that uses the placement syntax is a placement new expression

就像标准 new expression 调用的是标准 operator new()一样, placement new expression 调用的是placement版本的operator new(), 看起来很简单直白, 混在一起似乎也问题不大, 但是看看placement delete, 它就表示 placement operator delete() 函数, 因为根本不存在placement delete expression, 当我们调用

delete pObject;

我们调用的是标准的operator delete(). 为什么没有placement delete expression, C++的缔造者给出的解释是:

The reason that there is no built-in "placement delete" to match placement new is that there is no general way of assuring that it would be used correctly.

既然没有placement delete expression, 那为啥还需要placement operator delete(), 一个重要的原因是, C++需要placement operator delete()和placement operator new()成双成对. 假设一种情况:

当你调用placement new expression构建对象, 结果在构造函数中抛出异常, 这个时候怎么办, C++ 只能调用相应的placement operator delete(), 释放由placement operator new()获取的内存资源, 否则就会有内存泄露. 通过下面的例子可以感受一下:

#include <cstdlib>
#include <iostream>

struct A {} ;
struct E {} ;

class T {
public:
    T() { throw E() ; }
} ;

void * operator new ( std::size_t, const A & )
    {std::cout << "Placement new called." << std::endl;}
void operator delete ( void *, const A & )
    {std::cout << "Placement delete called." << std::endl;}

int main ()
{
    A a ;
    try {
        T * p = new (a) T ;
    } catch (E exp) {std::cout << "Exception caught." << std::endl;}
    return 0 ;
}

placement operator new() 和 placement operator delete()

上文只是界定了expression和operator的区别, 那什么样的函数才叫placement operator new()和placement operator delete(), 他们和标准operator new()以及operator delete()的区别就添加了自定义参数. 但是必须遵守以下规则.

对于placement operator new(), 它的第一个函数参数必须是std::size_t, 表示申请的内存的大小; 对于placement operator delete(), 它的第一个函数参数必须是void *, 表示要释放的对象指针. 比如:

void * operator new (std::size_t) throw(std::bad_alloc);    // 标准版本
void * operator new (std::size_t, const std::nothrow_t &) throw(); // placement 版本
void operator delete (void *) throw(); // 标准版本
void operator delete (void *, const std::nothrow_t &) throw(); // placement 版本

请注意nothrow版本的new, 也是C++标准, 它就是通过placement new和placement delete实现的.相应的nothrow 版本的new expression是这样:

T *t = new(std::nothrow) T;

在用户自定义空间上构建对象, 是placement new的本意, 它也被做到C++标准中, 作为default placement:

void * operator new (std::size_t, void * p) throw() { return p ; }
void operator delete (void *, void *) throw() { }

相应的 placement new expression 使用起来就是这样:

void *buffer = malloc(sizeof(ClassA));
ClassA *ptr = new(buffer)ClassA();

mempool用到的placement new

实际应用中, 内存池用到最多, 值得一讲, 假设一个内存池的定义如下:

class Arena {
public:
    void* allocate(size_t);
    void deallocate(void*);
    // ...
};

通过定义placement new和placement delete作用于Arena

void* operator new(size_t sz, Arena& a)
{
    return a.allocate(sz);
}
void operator delete(void *ptr, Arena& a)
{
    return a.deallocate(ptr);
}

创建对象:

Arena a();
X *p = new(a)X;

现在的问题是怎么delete, 因为我们不能调用

delete p; 

这样只能启用的标准的operator delete(), 而不是针对Arena的placement版本, 为此, 我们只能这么做:

p->~X();
operator delete(p, a);

或者把两个操作封装成一个template:

    template<class T> void destroy(T* p, Arena& a)
    {
        if (p) {
             p->~T();       // explicit destructor call
             a.deallocate(p);
        }
    }

然后这么调用

destroy(p,a);

参考

上一篇下一篇

猜你喜欢

热点阅读