effective C++ 笔记-02-内存管理01

条款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函数必须实现下面功能中的一种:
  1. 产生更多的可用内存
  2. 安装另一个不同的new-handler函数
  3. 卸除new-handler
  4.  抛出std::bad_alloc或从std::bad_alloc继承的其他类型的异常
  5. 没有返回

在类中使用:

 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所做的:



  1. 调用标准set_new_handler函数,输入参数为X的出错处理函数。
  2. 调用全局operator new分配内存。
  3. 假设全局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,重要的是你必须为内存分配失败做好准备。!






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值