《Effective C++》条款解读

条款01:视C++为一个语言联邦

  1. C++包含四个次语言,C,面向对象C++,模板C++,STL。

条款02:尽量以const,enum,inline替换#define

  1. 使用const,enum代替宏变量,使用inline代替宏函数。
  2. 类中静态常量(static const)就算是赋了初值,也是声明式,只要不取它的地址可以不提供定义式,但是有的编译器会要求提供定义式,就需要在类外定义const int classname::value;这里可以不给初值,因为在声明式中已经赋过初值。有的编译器不允许在声明的时候赋初值,只能在定义的时候赋初值。一般将声明式放在头文件中,定义式放在实现文件中。
  3. 如果编译器不允许在声明的时候赋初值,但是类内又需要该静态常量的值定义数组,可以使用enum{value=5};完成代替。
  4. template inline函数可以代替宏函数,并且具有宏函数的效率和一般函数的可预期行为加类型安全性。

条款03:尽可能使用const

  1. 如果const出现在*左边,表示所指物是常量,如果const出现在*右边,表示指针自身是常量,不能改变所指对象,如果const出现在*两边,表示所指物和指针都是常量。
  2. 迭代器和指针规则相同,const iterator表示指针自身是常量,const_iterator表示所指物是常量。
  3. 如果函数的返回值是常量,应该在返回值处加const。
  4. const成员函数和非const成员函数可以形成重载
  5. 常量对象只能调用类的const成员函数,不能调用非const成员函数。
  6. const成员函数内不能更改类的成员变量,除非将成员变量的类型声明为mutable。const成员函数内也不能调用非const成员函数。

条款04:确定对象被使用前已先被初始化

  1. 为内置型对象进行手动初始化,因为C++并不保证初始化它们。C++的C部分不会初始化,但是C++的STL部分会进行初始化。
  2. 类在构造函数中使用初始化列表进行初始化,因为一次拷贝构造函数的成本比一次默认构造函数加一次赋值操作的成本低。但是如果类中定义了多个构造函数,并且有许多成员变量和父类,可以遗漏赋值成本低的变量,用赋值操作代替初始化列表,并且可以将赋值操作放在函数中供其他构造函数调用。
  3. 类有着固定的初始化次序,父类总是先于子类初始化,类中的成员变量按照声明的次序初始化。成员变量中如果包含类对象(组合),一般编译器同样是父类先于该类对象初始化。
  4. non-local static对象没有初始化次序。static对象包括全局对象,定义在namespace内的对象,在class内,函数内和在文件内被声明为static的对象,函数内定义的static对象称为local static对象,其他称为non-local static对象,解决方法是将每一个non-local static对象的初始化放在各自的函数内,并返回它们的引用。在使用这些static变量的地方通过调用该函数来返回该对象。

条款05:了解C++默默编写并调用哪些函数

  1. 编译器会为类创建默认构造函数,拷贝构造函数,赋值构造函数和析构函数。
  2. 如果类的成员变量中含有引用或者是const,则用户不能调用默认赋值构造函数,因为两个对象的该成员变量的引用本来是指向不同的变量,但是当出现赋值语句时,意味着改变一个对象的引用,指向另一个变量,这是不被允许的。const成员变量也是如此,如果出现类对象赋值给另一个类对象,其中一个类对象的常量成员意味着将改变自己的值。除非重写了赋值构造函数。

条款06:若不想使用编译器自动生成的函数,就该明确拒绝

  1. 如果不想用户使用拷贝构造函数和赋值构造函数,应该将它们声明为private,但是这种方法并不绝对安全,因为成员函数可以使用,为了进一步保证,可以只声明,不定义这两个函数。
  2. C11提供了delete函数显式“删除”函数。
  3. 为了将行为的错误检查提前到编译阶段,可以让类private继承一个Uncopyable类,这个类将拷贝构造函数和赋值构造函数声明为private。

条款07:为多态基类声明virtual析构函数

  1. 当子类对象由一个父类指针删除,但是父类的析构函数是非虚函数的话,其结果是未有定义的,通常是对象的子类部分并没有被析构,造成“局部销毁”的现象。
  2. 任何类只要定义了虚函数,都应该有虚析构函数。
  3. 如果类不被作为父类被继承,不要将其析构函数声明为虚函数,这会增加对象的体积,因为虚函数指针(指向虚函数表)会占用体积。
  4. 为抽象类声明一个纯虚析构函数,并且必须在类外为其提供一份定义(awov::~awov(){})。

条款08:别让异常逃离析构函数

  1. 析构函数可能包含抛出异常的代码,一种方法是用try...catch语句,在catch语句中可以选择执行abort,也可以什么都不做。但是这个方法并没有减少抛出异常的可能,只是给出抛出异常该怎么办。
  2. 另一种方法是将可能抛出异常的代码从析构函数中提出,交给用户自己去保证,并且在析构函数中同样包含处理异常的语句,实现双保险。

条款09:绝不在构造和析构过程中调用virtual函数

  1. 在类的构造函数和析构函数中绝对不调用virtual函数,包括其调用的所有函数内部也不能包含virtual函数。

条款10:令operator=返回一个reference to *this

  1. 令operator=返回一个reference to *this,此条款同样适用于所有赋值相同的运算。

条款11:在operator=中处理“自我赋值”

  1. 确保在自定义operator=中处理自我赋值,包括比较this和传入参数地址,周到的语句顺序,以及copy-and-swap。所谓copy-and-swap指根据传入参数新建一个对象,然后和*this交换。

条款12:复制对象时勿忘其每一个成分

  1. 拷贝构造函数和赋值构造函数都应该确保复制对象内的所有成员变量,包括父类的成员变量。
  2. 不要尝试在拷贝构造函数中调用赋值构造函数,也不能在赋值构造函数中调用拷贝构造函数。

条款13:以对象管理资源

  1. 为防止资源泄露,请使用RAII对象,它们在构造函数中获得资源,在析构函数中释放资源。

条款14:在资源管理类中小心coping行为

  1. 智能指针用于管理存储在堆内存的对象,有时你需要自定义资源管理类用于管理栈内存对象。
  2. 自定义资源管理类一定要管理好资源的拷贝,对拷贝行为一般有几种做法:禁止拷贝,引用计数,深拷贝,移动。

条款15:在资源管理类中提供对原始资源的访问

  1. 很多函数要求接收原始指针参数,所以每个资源管理类应该提供一个返回其所管理资源的方法,如get()。
  2. 返回原始资源可以通过显式转换或隐式转换,显式转换即通过调用函数返回,隐式转换即写一个类型转换函数,一般来说,显示转换更安全,隐式转换更方便。

条款16:成对使用new和delete时要采取相同形式

  1. new,delete和new[],delete[]要成对使用,因为分配单个对象和分配多个对象的内存布局不同(多个对象第一位是对象个数),如果成对使用没有采取相同形式,会造成未定义的行为。

条款17:以独立语句将newed对象置于智能指针

  1. 将创建对象这条语句单独执行,而不是在调用函数的时候同时执行(假设该函数有一个接收该对象的智能指针参数),这是因为new对象动作肯定发生在智能指针构造之前,但是函数的调用次序可以在这两个动作之前,中间或之后,假如函数调用在这两个动作之间进行,就会在new出对象之后引发异常,造成资源泄露。这是内存的reorder行为,由编译器决定,所以要将创建对象这条语句单独执行。

条款18:让接口容易被正确使用,不易被误用

  1. 要考虑用户可能会犯什么错误,提前对这个可能的错误做出限制或消除。常用的原则有,让type容易被使用,不易被误用。避免无端与内置类型不兼容。提供行为一致的接口。使用智能指针。使用const。

条款19:设计class犹如设计type

  1. 一个设计良好的类需要考虑很多问题,如新类应该如何创建和销毁?对象的初始化和赋值有什么区别?新类按值传递会有什么行为?哪些是合法值?是否有继承体系?需要什么样的类型转换?哪些操作符是合法的?什么样的函数是public或是private的?什么是新类的“未声明接口”?你的新类很一般化吗?你真的需要一个新类吗?等等等等。

条款20:宁以pass-by-reference-to-const替换pass-by-value

  1. 尽量以pass-by-reference-to-const替换pass-by-value。前者通常比较高效,并且可以避免切割问题(将子类对象以传值的方式传递给父类变量会造成子类部分被切割)。

  2. 以上规则并不适用内置类型,以及STL的迭代器和函数对象,对它们来说,传值更合适。

条款21:必须返回对象时,别妄想返回其reference

  1. 绝不能返回一个指针或引用指向局部变量,或是堆对象,或是局部静态变量(局部静态变量意味着所有类对象只有一份该变量,且不同类对象获取该值都是相同的,在多线程中,或者有的代码需要在多个对象中该值不相同时是不适用的)。
  2. 函数返回值可以参考条款3第三小条。

条款22:将成员变量声明为private

  1. 切记将成员变量声明为private,这使用户获得数据访问一致性(指都是通过()调用函数,没有直接使用变量的情况),划分访问控制,封装的保证。
  2. protected并不比public更具封装性

条款23:宁以non-member、non-friend替换member函数

  1. 函数封装性可以用成员函数的数量作为粗略的衡量,越多的成员函数表示越低的封装性。越多的东西被封装就意味着有更大的弹性去更改这个类,并且不被外界感知。
  2. 友元函数和成员函数的访问权限相同,对封装性有相同程度的冲击。
  3. namespace和class不同,前者可以跨越多个源文件(如std),但是后者不能。

条款24:若所有参数皆需类型转换,请为此采用non-member函数

  1. 在构造函数不为explicit的前提下,如果操作符函数(operator...)的所有参数都需要类型转换,且该操作符函数是成员函数,就会导致诸如4+Rational这样的运算不能成功调用operator+,这是因为只有当第一个参数的类型与参数列表中第一个参数类型相同才可以,所以要将operator+定义成非成员函数。

条款25:考虑写出一个不抛出异常的swap函数

  1. 当std::swap对你的类型效率不高时,应该提供一个成员函数swap,并保证这个函数不抛出异常(成员函数swap可以调用std::swap)。
  2. 当你提供了一个成员函数swap,也应该提供一个非成员函数swap来调用前者,这个非成员函数可能是一个模板函数,所以这个非成员函数swap不应该在std命名空间内。
  3. 如果是为自定义类全特化std::swap,可以定义template<> void swap<Widget>(),通常不允许改变std命名空间里的内容,但是可以全特化其中的函数。关于偏特化C++标准也有规定,只允许类模板的偏特化,不支持函数模板的偏特化,也不能在std命名空间中添加函数模板的重载版本。

条款26:尽可能延后变量定义式的出现时间

  1. 一个变量被提前定义出来之后,后续可能因为某些原因没有被使用过,但是你仍得付出变量的构造成本和析构成本
  2. 延迟变量定义到非给他赋初值时,因为构造之后再赋值的成本比初始化的成本高
  3. 如果变量在for循环中被使用,考虑变量在循环外定义,在循环内赋值,会调用1次构造函数和析构函数,n次赋值。变量在循环内初始化会调用n次构造函数和n次析构函数。除非赋值成本比构造+析构成本低,或者后续还需要用到这个变量,否则应该选择第二种方案。

条款27:尽量少做转型动作

  1. 强制类型转换并非只是告诉编译器把某种类型视为另一种类型,什么都没做,而是真的编译出运行时代码。且对象的布局方式和它们的地址计算方式随编译器的不同而不同。
  2. 强制类型转换会让我们写出一些看似正确实际错误的代码。比如说在子类中需要调用父类的方法,一种实现方式是,将this指向的对象强转成父类对象,然后用.操作符调用父类方法,但是实际上改变的只是其副本,而不是*this对象,正确的方法应该使用[父类::func],或者使用[this->调用virtual方法],虚函数会进行自动类型推断。
  3. dynamic_cast的许多实现版本执行速度相当慢。

条款28:避免返回handles指向对象内部成分

  1. handles包括对象的引用、指针和迭代器,表示用来取得某个对象的东西。
  2. 返回对象内部成员的handlers意味着外部函数可以改变内部数据,即使这个数据被声明为私有,这是一种破坏封装性的行为。如果非要返回内部成员,可以返回const &,使得该成员不被更改。
  3. 返回成员函数的handles同理,即使该函数是私有的,外部也可以调用。
  4. 可能导致dangling handles问题,即原先handles所指对象不复存在,因为handles可能比其所指对象的寿命更长。

条款29:为“异常安全”而努力是值得的

  1. 带有异常安全性的函数不会泄露任何资源,不会造成数据破坏。反之,不具有异常安全性的函数可能会造成泄露资源,或者数据被破坏。
  2. 异常安全函数具有三个保证之一:基本保证,如果异常被抛出,程序依然保持在有效状态,没有对象或数据结构被破坏,但是程序的现实状态不可预料,可能和异常前状态一样,也可能是其他有效状态。强烈保证,要么程序执行成功,要么回滚至之前的状态。不抛出异常保证,承诺程序绝不抛出异常。
  3. 往往可以通过“copy-and-swap”实现强烈保证,“copy-and-swap”指为打算修改的对象创建一份副本,在副本上做出修改,如果异常抛出则返回源对象,如果执行成功则交换副本和源对象。但是因为代码中的短板效应,意味着系统的所有部分都要实现强烈保证,在现实上可能达不到。
  4. 如果有一个函数不具备异常安全性,整个系统就不具备异常安全性,所以函数提供的异常安全保证通常最高只是其他函数的异常安全保证的最弱等级。

条款30:透彻了解inlining的里里外外

  1. inline函数能避免函数调用带来的开销,且编译器最优化机制通常都用来浓缩不含函数调用的代码,因此编译器有能力对函数本体执行最优化。
  2. inline可能会增加目标代码的大小,过度热衷inlining会造成程序体积太大,可能会导致额外的换页行为,降低指令高速缓存的命中率。
  3. inline只是对编译器的申请,而不是强制命令。
  4. inline函数和templates函数通常被定义于头文件中,因为inlining在大多数程序中是编译器行为,为了将一个“函数调用”替换为“被调用函数的本体”,编译器必须知道这个函数长什么样子。templates一旦被使用,编译器为了将它具体化,也需要知道它长什么样子。如果所有由该templates具体化得到的函数都应该inline,那可以将templates声明为inline,否则不应该。
  5. 不能将虚函数声明为inline,因为virtual意味着直到运行时才确定调用哪个函数,替换是编译时行为。
  6. 不能将构造函数和析构函数声明为inline,因为即使一个空构造函数或析构函数,编译器也会为其填充构造成员变量或是父类对象,还有保证异常安全的代码。声明为inline会导致这些代码插入到继承它的子类的对应位置。
  7. 不能调试inline函数。因为inline函数被定义的地方并不是代码执行的地方,真正被执行的地方是inline函数被展开的地方。

条款31:将文件间的编译依存关系降至最低

  1. 当头文件中接口和实现并未分离时,如果其中任何一处被改变,或者头文件所依赖的其他头文件有任何改变,那么每个包含该头文件的文件也必须重新编译。
  2. 实现类的接口和其实现的分离,应该将类分为两个类,一个定义接口,一个实现接口。在定义接口的类中包含指向实现接口类的指针成员,这种设计被称为pimpl idiom(pointer to implementation),客户只include类的定义,而不知道具体实现,如此当接口实现变化时,并不会影响客户编写的代码。
  3. 将声明和定义放在不同的头文件中。
  4. 实现接口和其实现分离的一种方式是将类定义为接口,其中通常不带成员变量,也没有构造函数,只有虚析构函数和纯虚函数。由工厂函数或虚构造函数(静态成员函数)创建该类的对象。
  5. 接口和实现分离会带来额外的成本和开销,可以考虑以渐进方式使用这项技术。

条款32:确定你的public继承塑模出is-a关系

  1. public继承意味着is-a,适用于base class的每一件事情一定也适用于derived class,因为每一个derived class对象都是一个base class对象(Liskov Substitution Principle)。反之则不成立,并不是所有适用于子类的函数都适用于父类。
  2. 但是有时不严谨的继承关系会打破这一原则。如在鸟类中定义函数fly,企鹅也是一种鸟,但是企鹅并不会飞。正方形是一种矩形,按说所有适用于矩形的操作也适用于正方形,但是矩形的长宽可以不等,正方形的长宽必须相等。
  3. 类之间除了is-a的关系,还有has-a和is-implemented-in-terms-of(根据某物实现出)的关系。

条款33:避免遮掩继承而来的名称

  1. 继承中子类的作用域是被嵌套在父类的作用域内的,即子类会优先查找定义在自己内部的函数,查找不到函数名称才会去父类的作用域中查找。

  2. 当父类和子类中出现同名函数时(不管是non-virtual函数,还是virtual函数),定义在子类中的函数会遮掩在父类中定义的同名函数,即使该同名函数在父类中是重载函数,而且子类并没有定义出全部重载函数,子类对象也不会调用父类中符合的函数,即子类把父类中所有的同名函数都屏蔽了。这违背了条款32,父类对象和子类对象并不是is-a的关系

  3. 在子类中使用using Base::fun;可以暴露出父类中定义的全部同名函数。

  4. 有些情况下你只希望暴露出父类定义的同名函数的一个或几个(而不是全部),而又不想违背条款32,可以使用private继承,还有转交函数,转交函数是在子类中定义一个你想暴露父类函数的同名函数(参数类型和返回类型也相同),在函数中调用Base::fun;

条款34:区分接口继承和实现继承

  1. 接口继承和实现继承不同,public继承下总是会继承接口。
  2. 声明纯虚函数的目的是让子类只继承函数接口,子类必须实现继承来的纯虚函数。从子类对象中调用父类的纯虚函数是可以的,但是意义不大。
  3. 声明虚函数的目的是让子类继承函数的接口和默认实现。但是当子类忘记实现自己的虚函数,使用父类的默认实现时,又可能造成风险,解决办法是让父类声明纯虚函数和一个默认实现函数,在子类重写纯虚函数时,调用父类的默认实现函数。也可以在父类外实现父类的纯虚函数。
  4. 声明非虚函数的目的是让子类继承接口和一份强制实现,它不该在子类中被重新定义,非虚函数的表示其不变性凌驾特异性。
  5. 两个常见的错误,第一个是将所有的成员函数声明为non-virtual,第二个是将所有的成员函数声明为virtual。

条款35:考虑virtual函数以外的其他选择

  1. 使用non-virtual interface(NVI),它是Template Method设计模式的一种特殊形式,以public non-virtual成员函数包装降低访问级别(private或protected)的virtual函数,前提是包装器函数内的执行流程是稳定的,而具体步骤函数的实现是变化的,从而实现抽象不应该依赖实现细节,实现细节应该依赖抽象。
  2. 将virtual函数替换为“函数指针的成员变量”,这是Strategy设计模式的一种分解表现形式,在构造函数中接收函数指针,赋给函数指针的成员变量。这样做法的风险在于如果外部传入函数依赖类内non-public方法,就会使得类要弱化封装程度(提高访问级别或声明为friend函数)。
  3. 使用functor替换virtual函数,允许任何可调用物搭配一个兼容需求的签名式(函数名称,参数类型,返回类型),这也是Strategy设计模式的形式,在构造函数中接收一个可调用对象,包括普通函数指针,函数对象,成员函数指针。
  4. 将继承体系的virtual函数替换为另一个继承体系的virtual函数,这是Strategy设计模式的传统实现手法,在类中包含另一个类的成员变量,由这个类定义virtual函数,由他的子类继承并定义实现出该函数。

条款36:绝不重新定义继承而来的non-virtual函数

  1. 非虚函数是静态绑定,虚函数是动态绑定,所以当一个子类对象的地址被赋值给父类指针和子类指针,通过这两个指针访问非虚函数,一个是定义在父类的方法,一个是定义在子类的方法,这就导致了二义性。
  2. 根据条款32:确定你的public继承塑模出is-a关系,和条款34:区分接口继承和实现继承中非虚函数的不变性凌驾其特异性。所以你应该遵循此条款。

条款37:绝不重新定义继承而来的缺省参数值

  1. 缺省参数值就是默认参数值,virtual函数是动态绑定(晚绑定),但是默认参数却是静态绑定(早绑定),如果子类在继承到的虚函数中重新定义传入的默认参数,并且创建了一个子类对象,这个对象中该函数的默认值是从父类中继承得到的,而不是重新定义的。c++坚持这么做的原因是考虑程序的执行速度和编译器实现上的简易度。
  2. 即使子类在继承到的虚函数中填写与父类相同的默认参数值,当父类的默认参数值改变时,子类的默认参数值也需要改变,这就带来了重新定义继承虚函数默认参数值的风险。

条款38:通过复合塑模出has-a或“根据某物实现出”

  1. public继承表示是is-a的关系,复合表示是has-a或is-implemented-in-terms-of的关系。如果是可以抽象的真实世界存在的事物,如人、汽车、视频画面等,这些对象属于应用域,而如果是软件中的人工制品,如缓冲区、锁、查找树等,这些对象属于实现域。当复合/组合发生在应用域内的对象则是has-a的关系,当发生在实现域则是is-implemented-in-terms-of的关系。
  2. 复合/组合关系和继承关系完全不同,当继承实现不了,或者违背原则时,应该考虑用组合来实现,并且组合是比继承更好的关系。

条款39:明智而审慎地使用private继承

  1. 如果类之间的继承关系是private,编译器不会自动将一个子类对象转换为一个父类对象,即不能使用父类指针接收子类对象。private继承来的父类所有成员的属性都会变成private,意味着只有实现部分被继承,接口部分应略去。所以private继承不表示子类和父类之间有什么关系,而是你单纯想复用父类中定义的函数。
  2. private继承意味着implemented-in-terms-of(根据某物实现出),条款38提到组合也产生这样的关系,但是要尽可能使用组合,必要时才使用private继承。

条款40:明智而审慎地使用多重继承

  1. 多重继承比单继承复杂,当子类继承的多于一个类中定义了相同名称的函数,就会导致歧义性,并且多重继承很容易形成“钻石型多重继承”,即一个父类被两个子类继承,这两个子类又同时被另一个类继承。“钻石型多重继承”在默认情况下会使得最底层的子类对象拥有两份最顶层父类对象的副本,要解决这个问题需要使用虚继承。
  2. 虚继承会增加对象的大小,增加对象初始化及赋值的复杂度,降低运行速度,如果最顶层父类不带有任何数据情况会好一些。
  3. 多重继承的确有其正当用途。

条款41:了解隐式接口和编译期多态

  1. 对于模板类/函数,接口是隐式的,基于有效表达式。隐式是指模板生成的代码是和传入的参数类型相关的,不同的参数类型就算是调用同名的函数,也可能会生成不同的代码。
  2. 多态则是通过template具体化和函数重载解析,发生在编译期。这是因为模板的具现化是发生在编译期的。

条款42:了解typename的双重意义

  1. 模板内出现的类型如果是依赖于模板参数,称之为从属类型,如果从属类型在类内呈嵌套状,我们称它为嵌套从属类型。如果类型并不依赖模板参数,这样的类型称为非从属类型。默认情况下,嵌套从属类型不是类型,除非你使用typename告诉编译器它是类型,如typename C::iterator,typename Base<T>::Nested。但是在子类继承位置和子类初始化列表里不应该加typename

条款43:学习处理模板化基类内的名称

  1. 在子类模板内通过this->使用父类模板内的成员类型,或者显示写出base::成员类型

条款44:将与参数无关的代码抽离 templates

  1. 模板会生成多个类和函数,所以任何模板代码都不该与某个造成膨胀的模板参数产生依赖关系。
  2. 因非类型模板参数而造成的代码膨胀,往往可以消除,做法是以函数参数或类成员变量替换模板参数。
  3. 因类型模板参数而造成的代码膨胀,往往可以降低,做法是让带有完全相同的二进制表述的具体类型共享实现代码。

条款45:运用成员函数模板接受所有兼容类型

  1. 当具有继承关系的类作为模板的类型参数时,生成的对象并不具有继承关系,所以要在模板类的构造函数中体现这种关系,但是又不能实现出所有类之间的关系,所以将构造函数也作为一个模板,但是模板成员函数的类型参数和模板类的类型参数并不相同,这样就可以接收所有具有继承关系的类型。
  2. 当你声明成员函数模板用于泛化拷贝构造和泛化赋值操作时,你还需要声明常规的拷贝构造函数和赋值操作。

条款46:需要类型转换时请为模板定义非成员函数

  1. 当编写类模板,而它提供与模板相关的函数支持“所有参数的隐式类型转换”,将这些函数定义为类模板内部的friend函数。

条款47:请使用traits classes表现类型信息

  1. STL五大迭代器类别:输入迭代器,输出迭代器,前向迭代器,双向迭代器,随机迭代器。
  2. Traits类使得“类型相关信息”在编译期可用,它们以模板和模板特化来实现。

条款48:认识template元编程

  1. 模板元编程可将工作由运行期移至编译期,因此可以实现早期错误侦察和更高的执行效率。

条款49:了解new-handler的行为

  1. set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。

条款50:了解new和delete的合理替换时机

  1. 有许多理由需要写自定义的new和delete,包括改善性能、对堆运用错误进行调试、收集heap使用信息等。

条款51:编写new和delete时需固守常规

  1. operator new应该内含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就该调用new-handler。它也应该有能力处理0bytes内存申请。

条款52:写了placement new也要写placement delete

  1. 当你写一个placement new,也应该写出对应的placement delete,否则程序可能会发生内存泄露。
  2. 当你声明了placement new和placement delete,请确保不会隐藏正常版本。

条款53:不要轻忽编译器的警告

条款54:让自己熟悉包括TR1在内的标准程序库

条款55:让自己熟悉Boost

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值