分类: C/C++
2013-01-19 00:38:41
Item 49: Understand the behavior of the new-handler
先参考: ,回顾下new。PS:这网站看名字就知道不错。
最常见的new,像 int *p = new int; 这种称为 new-expression,它的原型两种:
::(optional) new (placement_params)(optional) ( type ) initializer(optional)
::(optional) new (placement_params)(optional) type initializer(optional)
可以结合C++0x中的auto关键字来使用:auto p = new auto('c');
上面的p类型就是char *类型,如果使用了auto,那么initializer就不是optional了,必须要写。(之前这里写成不能写了)
使用 new 创建多维数组时,第一维可以是一个值为正整数的数学表达式,也只有第一维可以。
直接分配在栈区的数组是不可以的。
int n = 42;
double a[n][5]; // Error
auto p1 = new double[n][5]; // OK
auto p2 = new double[5][n]; // Error
new和delete可以重载定制自己的版本,编译器会在当前空间首先寻找,即一个类成员函数使用new时,会先在类内空间寻找是否有定制版本,一层层寻找。
直接调用默认版本可以使用全局域作用符, ::new。
initializer如果没有写,则会调用默认的构造函数创建对象。
class base { public: base(int f=4):i(f){cout<<"in cons i:"< 输出如下,第一个不带初始化参数,第二个带
in cons i:4
in cons i:6第二个是operator new,在标准头文件
里面,不过不导入也能用,它默认隐含在每一个编译单元(文件)。它的作用是只分配内存,不调用对象构造函数进行初始化。
void* operator new ( std::size_t count ); void* operator new[]( std::size_t count ); void* operator new ( std::size_t count, const std::nothrow_t& ); void* operator new[]( std::size_t count, const std::nothrow_t& ); void* operator new ( std::size_t, void* ptr ); void* operator new[]( std::size_t, void* ptr );带[]是用于分配数组的,例如 new int[5],实际内部调用的就是operator new[],当然这种会有一些其他额外(overhead)开销,比如额外分配空间记住分配了多少个元素啊什么的。int main() { base * p = (base *)operator new (sizeof(base)); delete p; return 0; }这个程序看不到base构造函数中的输出,其实看看它的参数,size_t类型就可以明白,还有其返回值是 void*,注意转换。
第一行版本如果分配内存失败,会调用一个用户注册的回调函数进行处理,就是本item标题中提到的:
typedef void (*new_handler)();
new_handler set_new_handler(new_handler) throw();这些都在
里面,c++0x中还多一个new_handler get_new_handler(),不过gcc4.6里面还没看到。 回调函数完成后,会抛出std::bad_alloc异常。
第三行版本是老式c风格的版本,和第一个版本差异是不抛出异常,返回值为NULL,第二个参数可以写nothrow,
里面的一个全局变量。 extern const nothrow_t nothrow;
忘了在哪看的了,const变量定义如果没有extern,则其它文件不能引用的。
第五行版本直接返回第二个入参,说是给placement_new用的,没明白,那要第一个参数干吗。
placement_new就是new的第三种形式,它不用于分配内存,只是在已分配的内存上创建对象。
申请内存涉及到OS,对程序而言开销是很大的,当两个程序共享内存时就可以直接创建对象了,这只是一种场景。
语法形式是:
new (place_address) type
new (place_address) type (initializer-list)int main() { base * p = (base *)operator new (sizeof(base)); new( p) base(2) ; p->~base(); delete p; cout<<"end in main\\n"; return 0; }in cons i:2
end in main
要注意的是由于这里是手动创建对象,也就需要手动调用该对象的析构函数,释放其占据的资源,最后归还内存。
这里要注意下:调用的是默认的delete,其本身就会触发调用地址p上对象的析构函数,即如果~base()函数中有打印的话,会看到两次,
一次是手动调用的,一次是delete触发的。如果调用的是operator delete,则只会看到一次:
operator delete(p);
scott在这个item强调的是最好不要使用那个不抛出异常的new,虽然看着判NULL和c风格很兼容。申请对象内存空间成功时,不代表对象一定能创建成功,比如其构造函数内又申请内存等等。只要一个地方有抛异常的new,就有可能出现异常并扩散,没有捕获代码的话将是杯具。
不过google编程规范里不用异常,那也是他们最初就没有用,使用的话改动太多吧,难道都是判NULL的。