条款5:对应的new和delete要采用相同的形式
下面的语句有什么错?
string *stringArray = new string[100]; ... delete stringArray;
stringArray指向的100个string对象中的99个不会被正确地摧毁,因为他们的析构函数永远不会被调用。用new的时候会发生两件事。首先,内存被分配,然后,为被分配的内存调用一个或多个构造函数。用delete的时候,也有两件事发生:首先,为将被释放的内存调用一个或多个析构函数,然后,释放内存。
string *stringPtr1 = new string;
string *stringPtr2 = new string[100];
...
delete stringPtr1; // 删除一个对象
delete [] stringPtr2; // 删除对象数组
如果你在stringPtr1前加了"[]"会怎样呢?答案是:那将是不可预测的;如果你没在stringPtr2前没加上"[]"又会怎样呢?答案也是:不可预测。所以,解决这类问题的规则很简单:如果你调用new时用了[],调用delete时也要用[]。如果调用new时没有用[],那调用delete时也不要用[]这个规则对喜欢用typedef的人来说也很重要,因为写typedef 的程序员必须告诉别人,用new创建了一个typedef 定义的类型的对象后,该用什么形式的delete来删除。
typedef string AddressLines[4]; // 一个人的地址,共4行,
//每行一个string
string *pal = new AddressLines; // 注意"new AddressLines"
//返回string*, 和
// "new string[4]"返回的一样
delete pal; // 错误!
delete [] pal; // 正确
为了避免混乱,最好杜绝对数组类型用typedefs。这其实很容易,因为标准C++库(见条款49)包含有stirng和vector模板,使用他们将会使对数组的需求减少到几乎零。
条款6:析构函数里对指针成员调用delete
删除空指针是安全的,在你的析构函数里你就可以只用简单地delete掉他们,而不用担心他们是不是被new过。
你当然不会用delete去删除一个没有用new来初始化的指针,你也永远不会去删除一个传递给你的指针。换句话说,除非类成员最初用了new,否则是不用在析构函数里用delete的。
说到智能指针,这里介绍一种避免必须删除指针成员的方法,即把这些成员用智能指针对象来代替。
条款7:预先准备好内存不够的情况
operator new在无法完成内存分配请求时会抛出异常。你有时会不去管它,也许一直没去管它。但你心里一定还是深深地隐藏着一种罪恶感:万一new真的产生了异常怎么办?
一种解决方案:
#define NEW(PTR, TYPE) \
try { (PTR) = new TYPE; } \
catch (std::bad_alloc&) { assert(0); }
说明:bad_alloc是operator new不能满足内存分配请求时抛出的异常类型。assert是个宏。这个宏检查传给它的表达式是否非零,如果不是非零值,就会发出一条出错信息并调用abort。assert 只是在没定义标准宏NDEBUG的时候,即在调试状态下才这么做。
问题:1.具有宏的通病,2.没有考虑到new有各种各样的使用方式。比如:
new T;
new T(constructor arguments);
new T[size];
另一种解决方案:
当内存分配请求不能满足时,调用你预先指定的一个出错处理函数。
这个方法基于一个常规,即当operator new不能满足请求时,会在抛出异常之前调用客户指定的一个出错处理函数——一般称为new-handler函数。
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
set_new_handler的 输入参数是operator new分配内存失败时要调用的(新的)出错处理函数的指针, 返回值是set_new_handler 没调用之前就已经在起作用的
旧的出错处理函数的指针。
使用方法:
// function to call if operator new can't allocate enough memory
void noMoreMemory()
{
cerr << "Unable to satisfy request for memory\n";
abort();
}
int main()
{
set_new_handler(noMoreMemory);
int *pBigDataArray = new int[100000000];
...
}
一个设计得好的new-handler函数必须实现下面功能中的一种:
- 产生更多的可用内存
- 安装另一个不同的new-handler函数
- 卸除new-handler
- 抛出std::bad_alloc或从std::bad_alloc继承的其他类型的异常
- 没有返回
在类中使用:
class X { public: static new_handler set_new_handler(new_handler p); static void * operator new(size_t size); private: static new_handler currentHandler; };
new_handler X::currentHandler; // 缺省设置currentHandler为0(即null)
new_handler X::set_new_handler(new_handler p) { new_handler oldHandler = currentHandler; currentHandler = p; return oldHandler; }
最后看看X的operator new所做的:
- 调用标准set_new_handler函数,输入参数为X的出错处理函数。
- 调用全局operator new分配内存。
- 假设全局operator new为类型X的对象分配内存成功,, X的operator new会再次调用标准set_new_handler 来恢复最初的全局出错处理函数。
实现:
void * X::operator new(size_t size)
{
new_handler globalHandler = // 安装X的new_handler
std::set_new_handler(currentHandler);
void *memory;
try { // 尝试分配内存
memory = ::operator new(size);
}
catch (std::bad_alloc&) { // 恢复旧的new_handler
std::set_new_handler(globalHandler);
throw; // 抛出异常
}
std::set_new_handler(globalHandler); // 恢复旧的new_handler
return memory;
}
使用:
void noMoreMemory(); // X的对象分配内存失败时调用的
// new_handler 函数的声明
//
X::set_new_handler(noMoreMemory);
// 把noMoreMemory设置为X的
// new-handling函数
X *px1 = new X; // 如内存分配失败,
// 调用noMoreMemory
string *ps = new string; // 如内存分配失败,
// 调用全局new-handling函数
//
X::set_new_handler(0); // 设X的new-handling函数为空
//
X *px2 = new X; // 如内存分配失败,立即抛出异常
// (类X没有new-handling函数)
使用模板类:
定义:
emplate<class T> // 提供类set_new_handler支持的
class NewHandlerSupport { // “混合风格”的基类
public:
static new_handler set_new_handler(new_handler p);
static void * operator new(size_t size);
private:
static new_handler currentHandler;
};
template<class T>
new_handler NewHandlerSupport<T>::set_new_handler(new_handler p)
{
new_handler oldHandler = currentHandler;
currentHandler = p;
return oldHandler;
}
template<class T>
void * NewHandlerSupport<T>::operator new(size_t size)
{
new_handler globalHandler =
std::set_new_handler(currentHandler);
void *memory;
try {
memory = ::operator new(size);
}
catch (std::bad_alloc&) {
std::set_new_handler(globalHandler);
throw;
}
std::set_new_handler(globalHandler);
return memory;
}
// this sets each currentHandler to 0
template<class T>
new_handler NewHandlerSupport<T>::currentHandler;
使用:有了这个模板类,对类X加上set_new_handler功能就很简单了:只要让X从newHandlerSupport<X>继承:
// note inheritance from mixin base class template. (See
// my article on counting objects for information on why
// private inheritance might be preferable here.)
class X: public NewHandlerSupport<X> {
... // as before, but no declarations for
}; // set_new_handler or operator new
不管是用“正规”(即抛出异常)形式的new还是“无抛出”形式的new,重要的是你必须为内存分配失败做好准备。!