理解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);