理解C++ placement语法

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

关于new表达式

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

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

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

  • 在堆(heap)空间上申请一块空间, 大小等于sizeof(new-type-id)
  • 在申请的空间上构建对象, 即调用对象的构造函数

另外, 我了解到, 如果用户希望在自定义内存空间上构造对象, 可以调用另一个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);

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容