Effective C++ 2e Item7

第1期技术: DQN算法原理及实现过程 利用神经网络近似值函数的方法表示为: V^(s,w)≈Vπ(s)q^(s,a,w)≈qπ(s,a) \hat{V}(s, w) \approx V_{\pi}(s) \\ \hat{q}(s, a, w) \approx q_{\pi}(s, a) V^(s,w)≈Vπ​(s)q^​(s,a,w)≈qπ​(s,a) 那么具体的工作过程是怎样实现的? 以及如何从端到端的过程,本文将讲解Deep Q Network(DQN, 而这正是由DeepMind于2013年和2015年分别提出的两篇论文《Playi 阅读详情

条款7:预先准备好内存不够的情况

operator new在无法完成内存分配请求时会抛出异常(以前的做法一般是返回0,一些旧一点的编译器还这么做。你愿意的话也可以把你的编译器设置成这样。关于这个话题我将推迟到本条款的结尾处讨论)。大家都知道,处理内存不够所产生的异常真可以算得上是个道德上的行为,但实际做起来又会象刀架在脖子上那样痛苦。所以,你有时会不去管它,也许一直没去管它。但你心里一定还是深深地隐藏着一种罪恶感:万一new真的产生了异常怎么办?

你会很自然地想到处理这种情况的一种方法,即回到以前的老路上去,使用预处理。例如,C的一种常用的做法是,定义一个类型无关的宏来分配内存并检查分配是否成功。对于C++来说,这个宏看起来可能象这样:
    #define NEW(PTR, TYPE)                       /
        try { (PTR) = new TYPE; }                /
        catch (std::bad_alloc&) { assert(0); }
(“慢!std::bad_alloc是做什么的?”你会问。bad_alloc是operator new不能满足内存分配请求时抛出的异常类型,std是bad_alloc所在的名字空间(见条款28)的名称。“好!”你会继续问,“assert又有什么用?”如果你看看标准C头文件<assert.h>(或与它相等价的用到了名字空间的版本<cassert>,见条款49),就会发现assert是个宏。这个宏检查传给它的表达式是否非零,如果不是非零值,就会发出一条出错信息并调用abort。assert只是在没定义标准宏NDEBUG的时候,即在调试状态下才这么做。在产品发布状态下,即定义了NDEBUG的时候,assert什么也不做,相当于一条空语句。所以你只能在调试时才能检查断言(assertion))。

NEW宏不但有着上面所说的通病,即用assert去检查可能发生在已发布程序里的状态(然而任何时候都可能发生内存不够的情况),同时,它还在C++里有另外一个缺陷:它没有考虑到new有各种各样的使用方式。例如,想创建类型T对象,一般有三种常见的语法形式,你必须对每种形式可能产生的异常都要进行处理:
    new T;

    new T(constructor arguments);

    new T[size];

这里对问题大大进行了简化,因为有人还会自定义(重载)operator new,所以程序里会包含任意个使用new的语法形式。

那么,怎么办?如果想用一个很简单的出错处理方法,可以这么做:当内存分配请求不能满足时,调用你预先指定的一个出错处理函数。这个方法基于一个常规,即当operator new不能满足请求时,会在抛出异常之前调用客户指定的一个出错处理函数——一般称为new-handler函数。(operator new实际工作起来要复杂一些,详见条款8)

指定出错处理函数时要用到set_new_handler函数,它在头文件<new>里大致是象下面这样定义的:
    typedef void (*new_handler)();
    new_handler set_new_handler(new_handler p) throw();

可以看到,new_handler是一个自定义的函数指针类型,它指向一个没有输入参数也没有返回值的函数。set_new_handler则是一个输入并返回new_handler类型的函数。

set_new_handler的输入参数是operator new分配内存失败时要调用的出错处理函数的指针,返回值是set_new_handler没调用之前就已经在起作用的旧的出错处理函数的指针。

可以象下面这样使用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];

  ...

}

假如operator new不能为100,000,000个整数分配空间,noMoreMemory将会被调用,程序发出一条出错信息后终止。这就比简单地让系统内核产生错误信息来结束程序要好。(顺便考虑一下,假如cerr在写错误信息的过程中要动态分配内存,那将会发生什么...)

operator new不能满足内存分配请求时,new-handler函数不只调用一次,而是不断重复,直至找到足够的内存。实现重复调用的代码在条款8里可以看到,这里我用描述性的的语言来说明:一个设计得好的new-handler函数必须实现下面功能中的一种。
    ·产生更多的可用内存。这将使operator new下一次分配内存的尝试有可能获得成功。实施这一策略的一个方法是:在程序启动时分配一个大的内存块,然后在第一次调用new-handler时释放。释放时伴随着一些对用户的警告信息,如内存数量太少,下次请求可能会失败,除非又有更多的可用空间。
    ·安装另一个不同的new-handler函数。如果当前的new-handler函数不能产生更多的可用内存,可能它会知道另一个new-handler函数可以提供更多的资源。这样的话,当前的new-handler可以安装另一个new-handler来取代它(通过调用set_new_handler)。下一次operator new调用new-handler时,会使用最近安装的那个。(这一策略的另一个变通办法是让new-handler可以改变它自己的运行行为,那么下次调用时,它将做不同的事。方法是使new-handler可以修改那些影响它自身行为的静态或全局数据。)
    ·卸除new-handler。也就是传递空指针给set_new_handler。没有安装new-handler,operator new分配内存不成功时就会抛出一个标准的std::bad_alloc类型的异常。
    ·抛出std::bad_alloc或从std::bad_alloc继承的其他类型的异常。这样的异常不会被operator new捕捉,所以它们会被送到最初进行内存请求的地方。(抛出别的不同类型的异常会违反operator new异常规范。规范中的缺省行为是调用abort,所以new-handler要抛出一个异常时,一定要确信它是从std::bad_alloc继承来的。想更多地了解异常规范,参见条款M14。)
    ·没有返回。典型做法是调用abort或exit。abort/exit可以在标准C库中找到(还有标准C++库,参见条款49)。

上面的选择给了你实现new-handler函数极大的灵活性。

处理内存分配失败的情况时采取什么方法,取决于要分配的对象的类:
class X {
public:
  static void outOfMemory();

  ...

};

class Y {
public:
  static void outOfMemory();

  ...

};

X* p1 = new X;      // 若分配成功,调用X::outOfMemory

Y* p2 = new Y;      // 若分配不成功,调用Y::outOfMemory

C++不支持专门针对于类的new-handler函数,而且也不需要。你可以自己来实现它,只要在每个类中提供自己版本的set_new_handler和operator new。类的set_new_handler可以为类指定new-handler(就象标准的set_new_handler指定全局new-handler一样)。类的operator new则保证为类的对象分配内存时用类的new-handler取代全局new-handler。

假设处理类X内存分配失败的情况。因为operator new对类型X的对象分配内存失败时,每次都必须调用出错处理函数,所以要在类里声明一个new_handler类型的静态成员。那么类X看起来会象这样:
    class X {
    public:
      static new_handler set_new_handler(new_handler p);
      static void * operator new(size_t size);

    private:
      static new_handler currentHandler;
    };

类的静态成员必须在类外定义。因为想借用静态对象的缺省初始化值0,所以定义X::currentHandler时没有去初始化。
new_handler X::currentHandler;      // 缺省设置currentHandler为0(即null)

类X中的set_new_handler函数会保存传给它的任何指针,并返回在调用它之前所保存的任何指针。这正是标准版本的set_new_handler所做的:
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的出错处理函数。这使得X的new-handler函数成为全局new-handler函数。注意下面的代码中,用了"::"符号显式地引用std空间(标准set_new_handler函数就存在于std空间)。

2. 调用全局operator new分配内存。如果第一次分配失败,全局operator new会调用X的new-handler,因为它刚刚(见1.)被安装成为全局new-handler。如果全局operator new最终未能分配到内存,它抛出std::bad_alloc异常,X的operator new会捕捉到它。X的operator new然后恢复最初被取代的全局new-handler函数,最后以抛出异常返回。

3. 假设全局operator new为类型X的对象分配内存成功,, X的operator new会再次调用标准set_new_handler来恢复最初的全局出错处理函数。最后返回分配成功的内存的指针。

C++是这么做的:
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;
}

如果你对上面重复调用std::set_new_handler看不顺眼,可以参见条款M9来除去它们。

使用类X的内存分配处理功能时大致如下:
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函数)
                                               //

你会注意到,处理以上类似情况,如果不考虑类的话,实现代码是一样的,这就很自然地想到在别的地方也能重用它们。正如条款41所说明的,继承和模板可以用来设计可重用代码。在这里,我们把两种方法结合起来使用,从而满足了你的要求。

你只要创建一个“混合风格”(mixin-style)的基类,这种基类允许子类继承它某一特定的功能——这里指的是建立一个类的new-handler的功能。之所以设计一个基类,是为了让所有的子类可以继承set_new_handler和operator new功能,而设计模板是为了使每个子类有不同的currentHandler数据成员。这听起来很复杂,不过你会看到代码其实很熟悉。区别只不过是它现在可以被任何类重用了。
template<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


使用X的时候依然不用理会它幕后在做些什么;老代码依然工作。这很好!那些你常不去理会的东西往往是最可信赖的。

使用set_new_handler是处理内存不够情况下一种方便,简单的方法。这比把每个new都包装在try模块里当然好多了。而且,NewHandlerSupport这样的模板使得向任何类增加一个特定的new-handler变得更简单。“混合风格”的继承不可避免地将话题引入到多继承上去,在转到这个话题前,你一定要先阅读条款43。

1993年前,C++一直要求在内存分配失败时operator new要返回0,现在则是要求operator new抛出std::bad_alloc异常。很多C++程序是在编译器开始支持新规范前写的。C++标准委员会不想放弃那些已有的遵循返回0规范的代码,所以他们提供了另外形式的operator new(以及operator new[]——见条款8)以继续提供返回0功能。这些形式被称为“无抛出”,因为他们没用过一个throw,而是在使用new的入口点采用了nothrow对象:
class Widget { ... };

Widget *pw1 = new Widget;      // 分配失败抛出std::bad_alloc if

if (pw1 == 0) ...              // 这个检查一定失败
Widget *pw2 =
  new (nothrow) Widget;        // 若分配失败返回0

if (pw2 == 0) ...              // 这个检查可能会成功

不管是用“正规”(即抛出异常)形式的new还是“无抛出”形式的new,重要的是你必须为内存分配失败做好准备。最简单的方法是使用set_new_handler,因为它对两种形式都有用。

正确解决std::bad_alloc异常的有效解决方法 阅读详情

相关推荐

【异常】IDEA提示没有足够的内存来执行请求的操作

收到“没有足够的内存来执行请求的操作,请增加’Xmx’设置并重启IDE以使更改生效”这类错误信息。

奶爸的编程之路,也就一周冷个三天 2184

C++ std::bad_alloc异常

1、问题 在对一个120w行的数据进行解析的时候,程序运行时出现了崩溃,系统提示出现了std::bad_alloc异常。 已知:在使用new分配内存空间时,内存空间不够时就会抛出该异常。 解决:在程序中加入delete和clear及时释放内存,并且对原始数据进行分割处理。 2、异常处理 当分配较大块内存时,进行内存分配失败的异常处理。避免程序的运行错误或崩溃。 利用try-catch模...

mercy_ps的博客 10万+

C++中bad_alloc类

当分配内存的请求没有被成功执行时,系统会抛出一个用bad_alloc类来描述出的异常。 1 定义 bad_alloc类的定义如下所示 classbad_alloc: publicexception { bad_alloc(); virtual~bad_alloc(); bad_alloc(constbad_alloc&); bad_alloc& operator=(constbad_alloc&); constchar*...

hou09tian的博客 3698

C++ 异常:std::bad_alloc, 内存不够了,如何减少代码内存?

对于非嵌入式开发中,空间复杂度相比于时间复杂度确实显得没那么重要,能用数组的都可以尽量避免使用链表,这样减少了代码复杂度,减少了时间复杂度。但是对于一些算法题,对内存有要求的就会出现问题。 正如标题所说的,出现bad_alloc异常,一查就知道原来是内存出现了问题。 我首先想到是会不会是堆栈溢出,但是很快被我否决,因为代码中我并没有使用递归,也最多就三层函数调用。于是我就查看自己定义的变量...

qq_40963043的博客 1万+

怎么获得积分啊,急!!!!急!!!!急!!!!急!!!!

如题 联想7150,不能装win764位,需要升级bios,没有积分下载不了,注册手机也没有积分

weixin_43744540的博客 138

关于数组的重组,急,急,急!!!!

Array (     [0] => 商标名称     [1] => 耀丽人生     [2] => 注册号/申请号     [3] => 4576058     [4] => 国际分类号     [5] => 25     [6] => 申请日期     [7] => 2005/4/1     [8] => 申请人名称(中文)     [9] => 施能波        

271

急!!!!

我点击拉取代码然后有个弹窗里面就没有数据了怎么回事。

weixin_65208215的博客 373

求帮忙!!!!急急急

小编码大脑门RU9CRC43aWdxNDs3NDFSOzFpa1I1MWliT08w获取Flag

weixin_42213572的博客 6471

oracle 00183,TSM备份oracle报错!!!!急!!!!

TSM备份oracle报错!!!!急!!!!客户前几天搬机房,现在tsm备份oracle报错(带库为ibm ts3100)报错如下-------------------------------------------------------Starting backup at 07-DEC-13channel c1: starting incremental level 0 datafile ba...

weixin_39940755的博客 253

Qt std :: bad_alloc

今天又是被BUG统治的一天,刚使用物理方法解决了“QVector "isDetached()"又来一个”std :: bad_alloc“,这个问题更是没法下手。只能摸索尝试,仅此记录。

DreamLife 4567

C++ 异常:std::bad_alloc,内存不足,new分配失败

C++ 异常:std::bad_alloc,内存不足,new分配失败

english72428的博客 7719

linux内存泄漏通用排查方法

一.概述 Linux下经常遇到内存泄漏的问题,尤其对C/C++开发人员来说是一个亘古不变的话题,现在介绍解决Linux内存泄漏问题的方法层出不穷,让人眼花缭乱,但是作为开发人员应该从本质上了解为何会发生内存泄漏,在面对内存泄漏的问题时应当知道相关的技术细节,在解决问题时应当有个固定的排查思路,要善用Linux系统本身提供的工具来定位和解决,而不是一味的通过各种各样不常用的、不熟悉的工具来排查问题,这样不仅耗时,最终不一定能够解决问题。 本文力求通过一种通用的方法来讲解如何在Linux下定位和解决内...

jersonborn521的专栏 1万+

程序bad_alloc分析

另外一个值得注意的是,这种情况在windows下测试未产生,在linux下就很快出现;因为windows处理性能比linux处理性能会慢,不会造成资源竞争,windows下可以看做是线性处理;看众多文章都说bad_alloc错误一般是内存泄漏,最近我不幸在开发中也遇到了,一直朝是否智能指针循环引用,未释放方向找了好几天,几经折腾均未解决;后来沉下心来,追溯数据源头,发现是在收到数据时放入缓存队列时未加锁,而后续又会取走缓存队列数据,这样就引起了读取数据时,可能被拿走,读取了非法指针;

aibao_b的专栏 1787

C++报错已解决】std::bad_alloc

C++开发过程中,报错是开发者经常会遇到的头疼问题。其中,std::bad_alloc这个报错信息可能会让很多开发者感到困惑。它的出现往往意味着程序在内存分配方面遇到了麻烦。那么,当我们遇到这个报错时,应该如何快速准确地解决呢?这篇文章将深入探讨这个问题,为开发者或者环境配置者提供一些有效的解决方法。

鸽芷咕的博客 7726

C++、异常处理

异常处理基本概念。

生活就像海洋 1696

std::bad_alloc 异常产生原因之一

const std::string &test() { return "123456"; } void main() { auto str = test(); } 调用上面的test()函数就会产生std::bad_alloc 异常,原因是return时构造了内容为123456的std::string变量,返回引用后变量就被析构了,再访问这个引用就会出现这个异常。 总结:const std::string &用来返回类的成员变量时效率高,不用重新构造返回的std:.

Heworld的博客 1万+

【debug】python代码报错std::bad_alloc

报错terminate called after throwing an instance of 'std::bad_alloc' what(): std::bad_alloc解决

向上生长的程序媛的博客 1677

Chrome 出现内存 不足

地址栏直接输入:chrome://flags/#automatic-tab-discarding这里默认default 改为Enable即可为了麻烦 不想每次 都输入网址改 或者认为Chrome 幺儿不听话了 直接来个快捷方式在Chrome中用Ctrl+d 建立一个书签 把上面网址复制到对应位置 保存起来即可...

主攻大数据 人工智能 物联网 安全 低空经济等方向。mtsc 、gtest特邀分享嘉宾 2万+

what(): std::bad_alloc

用Qt Creator写一个工具,然后连续长时间运行某个功能时,过一会就会报错: terminate called after throwing an instance of 'std::cad_alloc' what():std::bad_alloc 。 长时间运行该功能时,打开任务管理器,监视该exe占用内存情况,发现占用的内存随着时间增长也在增大,于是认为是有些指针重复申请了内存但是没有释放。debug了半天终于找到了,确实是有几个指针malloc了之后,还没跑到free的地方就退出了,下次继续m

fang20277的专栏 1万+

【华为OD机试真题 新系统】1029、字符串处理 | 机试真题+思路参考+代码解析(C++、Java、Py、C语言、JS)

【华为OD机试真题 新系统】1029、字符串处理 | 机试真题+思路参考+代码解析(C++、Java、Py、C语言、JS) 华为od机试,华为od机试真题,华为od机试E卷,华为odE卷,华为od机试真题E卷,华为OD,华为OD机试,华为OD机试真题,华为od机考,华为od机试D卷,华为od机试真题D卷,华为od手撕代码,华为od,华为od机试C卷,华为od e卷,华为2026e卷,od机试,2026E卷,华为OD机试E卷,什么是华为od,华为od介绍,全面了解华为od,2026华为od,如何备战华为od,

KJ.JK 190

FT4222 上位机 USB转IIC

通过python+PYQT5写的上位机,USB转 IIC测试过 windows 64位系统运行

上一篇: Effective C++ 2e Item6
下一篇: Effective C++ 2e Item8
lostmouse
博客等级 码龄26年 22粉丝 63原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值