C++程序员应了解的那些事(114)~Effective C++ :改善程序与设计的55个具体做法

文章介绍了C++编程中的一些最佳实践,强调了使用const来帮助编译器捕获错误,利用inline函数提高效率,以及如何妥善管理资源,包括使用智能指针避免内存泄漏。此外,还讨论了多态性,特别是声明virtual析构函数的重要性。
摘要由CSDN通过智能技术生成
章节标号条款细节
让自己习惯C++Item-1视C++为一个语言联邦
Item-2尽量以const、enum、inline替换#define
  • 对于单纯常量,最好以const对象或enums替换#defines
  • 对于形似函数的宏,最好用inline函数替换#defines
Item-3尽可能使用const
  • 将某些东西声明为const,可帮助编译器侦测出错误用法。

  • 编译器强制实施bitwise constness,但编写程序时应该使用“概念上的常量性”

  • 当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。

  • 两个成员函数如果只是常量性不同,可以被重载。

  • bitwise const:成员函数只有在不更改对象之任何成员变量(static除外)时才可以说是const;

  • logical const:一个const成员函数可以修改它所处理的对象内的某些bits,但只有在客户端检测不出的情况下才可以。(常用mutable来做到)

  • mutable:释放掉non-static成员变量的bitwise constness约束。

Item-4确定对象被使用前已先被初始化

        为内置型对象进行手工初始化,C++不保证初始化它们;

        构造函数最好使用成员初值列,而不要在构造函数体内使用赋值。初值列列出的成员变量,其排列次序应该和它们在class中的声明次序相同。

        C++规定:对象的成员变量的初始化动作发生在构造函数本体之前。

        C++对定义于不同编译单元内的non-local static对象的初始化次序并无明确定义。

        函数内的static对象称为local static对象;其他的static对象都是non-local static对象。

        对于内置型对象:初始化和赋值成本相同。

        为避免跨编译单元之初始化次序问题,请以local static对象替换non-local static对象:将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static),这些函数返回一个reference指向它所包含的对象。然后用户调用这些函数,而不是直接指涉这些对象。

构造/析构/赋值Item-5了解C++默默编写并调用那些函数
  • default构造函数和析构函数主要是给编译器一个地方用来放置“藏身幕后”的代码,像是调用base class和non-static成员变量的构造函数和析构函数。

  • 如果你打算在一个内含reference成员的class内支持赋值操作,你必须自己定义copy assignment操作符。面对内含const成员的class也是一样的,更改const成员是不合法的:(可以让引用所指的那个对象被修改,进而影响持有指针或reference而且指向该对象的其他对象)

Item-6若不想使用编译器自动生成的函数,则明确拒绝

        所有编译器产生的函数都是public,为了阻止这些函数被自动创建出来,只能先自行声明他们。我们可以将这些函数(copy构造函数或copy assignment操作符)声明为private,阻止他们被调用。

  • 但member函数和friend函数还是可以调用private函数。解决方案:将成员函数声明为private且故意不去实现。

  • noncopyable base class

  • C++11中,当我们定义一个类的成员函数时,如果后面使用"=delete"去修饰,那么就表示这个函数被定义为deleted,也就意味着这个成员函数不能再被调用。

Item-7为多态基类声明virtual析构函数
  • 当derived class对象经由一个base class指针被delete,而该base class带有一个non-virtual析构函数:结果未定义,通常发生的是对象的derived成分没被销毁。

  • 如果class不会被当做base class或不是为了具备多态性(通过base class接口处理derived class对象),令其析构函数为virtual往往是个馊主意。

  • 所有STL容器(如vector、list、set、map等)和std::string都不带virtual析构函数:所以拒绝继承一个标准容器或任何其他“带有non-virtual析构函数”的class。

Item-8别让异常逃离析构函数
  • 析构函数绝对不要吐出异常:如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吐下它们(不传播)或结束程序。

  • 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数)执行该操作。

Item-9绝不在构造和析构过程中调用virtual函数
  • base class构造/析构期间virtual函数绝对不会下降到derived classes阶层。

  • 在Derived class对象的base class构造期间,对象的类型是base class而不是Derived class。对象在Derived class构造函数开始执行前不会成为一个Derived class对象。

  • 一旦Derived class析构函数开始执行,对象内的Derived class成员变量便呈现未定义值,所以C++视它们仿佛不存在。进入base class析构函数后对象就成为一个base class对象,而C++的任何部分包括virtual函数、dynamic_cast等也就那么看待它。

  • 警告:pure virtual ‘virtual void Text::get(std::size_t)’ called from constructor

    (即使声明为纯虚,只要有定义则还是可以调用这个函数)

Item-10令operator=返回一个reference to *this        为了实现“连锁赋值”:赋值操作符必须返回一个reference指向操作符的左侧实参。
Item-11在operator=中处理“自我赋值”        确保当对象自我赋值时operator=有良好的行为:证同测试、精心周到的语句顺序、copy-and-swap。
Item-12复制对象时务忘其中每一个成分
  • Copying函数应该确保复制“对象内的所有成员变量”及所有“base class成分”;

  • 不要尝试以某copying函数实现另外一个copying函数。应该将共同机能放进第三个函数(init)中,并由两个copying函数共同调用;

  • 编译器生成版copying函数的行为:将被拷贝对象的所有成员变量都做一份拷贝;

资源管理Item-13以对象管理资源      把资源放进对象内,我们便可依赖C++的“析构函数自动调用机制”确保资源被释放。

       以对象管理资源的两个关键想法:

  • 获得资源后立刻放进管理对象(实际上以“对象管理资源的”观念常被称为“资源取得的时机便是初始化时机”,Resource Acquisition Is Initialization; RAII),因为我们几乎总是在获得一笔资源后于同一语句内以它初始化某个管理对象。

  • 管理对象运用析构函数确保资源被释放

       auto_ptr:若通过copy构造函数或copy assignment操作符复制它们,它们会变成null,而复制所得的指针将取得资源的唯一拥有权。

       由于auto_ptr被销毁时会自动删除它所指之物,所以我们一定要注意别让多个auto_ptr同时指向同一对象。

       引用计数型智慧指针(RCSP):所谓RCSP也是个智能指针,持续追踪共有多少对象指向某笔资源,并在无人指向它时自动删除该资源。例如tr1::shared_ptr

       auto_ptr和tr1::shared_ptr 两者都在析构函数中做delete而不是delete[]动作,这意味着在动态分配而得的array上使用auto_ptr和tr1::shared_ptr是个bad idea。

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

  • 两个常被使用的 RAII classes 分别是tr1::shared_ptr和auto_ptr。前者通常是较佳选择,因为其 copy 行为比较直观。若选择auto_ptr, 复制动作会使它(被复制物)指向 null

Item-14在资源管理类中小心copying行为常见的RAII class copying行为:
  • 禁止复制

  • 对底层资源使用“引用技术法”:shared_ptr允许指定所谓的“删除器”,那是一个函数或函数对象。

  • 复制底部资源:复制资源管理对象,应该同时也复制其所包覆的资源。也就是说复制资源管理对象时进行的是“深度拷贝”

  • 转移底部资源的拥有权。这是auto_ptr奉行的复制做法

Item-15在资源管理类中提供对原始资源的访问

显式转换:

auto_ptr和tr1::shared_ptr都提供一个get成员函数,用来执行显式转换,也就是它会返回智能指针内部的原始指针(的复件)

隐式转换:

就像(几乎)所有智能指针一样,auto_ptr和tr1::shared_ptr也重载了指针取值 (pointer dereferencing) 操作符 (operator->和 operator*) ,它们允许隐式转换至底部原始指针。

自定义资源管理类,可以定义隐式转换函数 operator OriginClass(){...}

Item-16成对使用new和delete时要采取相同形式
  • 使用new:①内存分配(通过operator new)②针对此内存会有一个或多个构造函数被调用

  • 使用delete:①针对此内存会有一个或多个析构函数被调用 ② 释放内存(通过operator delete)

       数组所用的内存通常还包括“数组大小”的记录,以便delete知道需要调用多少次析构函数。唯一能让delete知道内存中存在一个“数组大小”的记录方法是使用delete[]。

Item-17独立语句将newed对象置入智能指针        因为编译器对于“跨越语句的各项操作”没有重新排列的自由(只有在语句内编译器才拥有那个自由度);

       以独立语句将newed的对象存储于智能指针内,如果不这样做,一旦抛出异常,则可能导致内存泄露(在资源被创建和资源被转换为资源管理对象两个时间点之间可能会发生异常)。

设计与声明Item-18让接口容易被正确使用,不易被误用
  • 好的接口容易被正确使用,不容易被误用

  • “促进正确使用”的方法包括接口一致性,以及与内置类型的行为兼容

  • 阻止误用,明确而审慎的导入新类型对预防“接口被误用”有神奇疗效

  • tr1::shared_ptr支持定制型删除器(custom deleter)。这可以防范cross-DLL问题,可被用来自动解除互斥锁(mutexes)

        所谓的 "cross-DLL problem“ :这个问题发生于“对象在动态连接程序库(DLL) 中被new创建,却在另一个DLL内被 delete销毁"。在许多平台上,这一类“跨DLL new/delete 成对运用“会导致运行期错误。 tr1::shared_ptr 没有这个问题,因为它缺省的删除器是来自“tr1::shared_ptr诞生所在的那个 DLL"的delete 。tr1::shared_ptr会追踪记录所指对象的引用计数变成0时所需要调用的那个DLL's delete。

Item-19设计class犹如设计type        应该带着和“语言设计者当初设计语言内置类型时”一样的谨慎来研讨class的设计。

        你的新type需要什么样的转换?你的 type 生存于其他一海票types之间,因而彼此该有转换行为吗?

  • 如果你希望允许类型T1之物被隐式转换为类型T2之物,就必须在class T1内写一个类型转换函数 (operator T2) 或在 class T2 内写一个non-explicit-one-argument (可被单一实参调用)的构造函数。

  • 如果你只允许explicit构造函数存在,就得写出专门负责执行转换的函数,且不得为类型转换操作符 (type conversion operators)或 non-explicit-one-argument 构造函数。

Item-20宁以pass-by-reference-to-const替换pass-by-value

        除非另外指定,否则函数参数都是以实际参数的副本为初值,而调用端所获得的亦是函数返回值的一个副本。这些副本由对象的copy构造函数产出。

  • 以by reference方式传递,将它声明为const是必要的。

  • 以by reference方式传递,可以避免slicing(对象切割)问题。

  • 对内置类型、STL迭代器和函数对象,pass by value比 pass by reference方式效率高些。pass by value往往更合适。

Item-21必须返回对象时,别妄想返回其reference        C++允许编译器实现者实施最优化用以改善产出码的效率却不改变其可观察的行为。(因此某些情况下,operator*返回值的构造和析构可被安全的消除)
Item-22将成员变量声明为private
  •  将成员变量隐藏在函数接口的背后,可以为“所有可能的实现”提供弹性。
  •  切记将成员变量声明为private。。这可赋予客户访问数据的一致性、可细微划分访问控制、允诺约束条件获得保证,并提供class作者以充分的实现弹性。
  • protected并不比public更具封装性。
  • 从封装的角度看,其实只有两种访问权限:private(提供封装)和其他(不提供封装)
Item-23宁以non-member、non-friend替换member函数

        如果要你在一个member函数(它不只可以访问class内的private数据,也可以取用private函数、enums、typedefs 等等)和一个 non-member,non-friend 函数(它无法访问上述任何东西)之间做抉择,而且两者提供相同机能,那么,导致较大封装性的是 non-member、 non-friend 函数,因为它并不增加“能够访问class内之private成分"的函数数量。

        namespace和 classes 不同,前者可跨越多个源码文件而后者不能。

Item-24若所有参数皆需要类型转换,请为此采用non-member函数
  • 唯有非成员函数才有能力“在所有实参身上实施隐式类型转换”;
  • 只有当参数被列于参数列 (parameter list) 内,这个参数才是隐式类型转换的合格参与者。地位相当于”被调用之成员函数所隶属的那个对象”(即this对象)的那个隐喻参数,绝不是隐式转换的合格参与者。
  • 如果你需要为某个函数的所有参数(包括被 this 指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个 non-member。
Item-25考虑写出一个不抛出异常的swap函数

       PIMPL(pointer to implementation):以指针指向一个对象,内含真正数据。

       通常我们不能够改变 std 命名空间内的任何东西,但可以为标准 templates (如 swap) 制造特化版本,以使它专属于我们自己的class。

       C++ 只允许对 class templates 偏特化,在 function templates 身上偏特化是行不通的。当你打算偏特化一个 function template 时,惯常做法是简单地为它添加一个重载版本。

       一般而言,重载 function templates 没有问题,但std 是个特殊的命名空间,其管理规则也比较特殊。客户可以全特化 std 内的 templates, 但不可以添加新的 templates(或 classes或 functions 或其他任何东西)到 std 里头。

       如果你想让你的 “class 专属版 “swap 在尽可能多的语境下被调用,你需得同时在class 所在命名空间内写一个 non-member 版本以及一个 std: :swap 特化版本。

       如果 swap 缺省实现版的效率不足(那几乎总是意味你的 class或 template使用了某种 pimpl 手法),试着做以下事情:

1. 提供一个 public swap 成员函数,让它高效地置换你的类型的两个对象值,这个函数绝不该抛出异常。

2. 在你的 class 或template 所在的命名空间内提供一个 non-member swap, 并令它调用上述 swap 成员函数。

3. 如果你正编写一个 class (而非 class template) ,为你的 class 特化 std:: swap ,并令它调用你的 swap 成员函数。

4. 调用 swap 时应针对 std:: swap 使用 using 声明式,然后调用 swap 并且不带任何“命名空间资格修饰”

5. 为“用户定义类型”进行 std templates 全特化是好的,但千万不要尝试在std内加入某些对 std 而言全新的东西。

实现Item-26尽可能延后变量定义式的出现时间        不只应该延后变量的定义,直到非得使用该变量的前一刻为止;甚至应该尝试延后这份定义直到能够给它初值实参为止。
Item-27尽量少做转型动作

        尽量避免转型,特殊是在注重效率的代码中避免dynamic_cast;

        如果转型是必要的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需将转型放进他们自己的代码;

        宁可使用C++ Style(新式)转型,不要使用旧式转型(前者很容易辨识出来)。

  • const_cast:用来将对象的常量性转除

  • dynamic_cast:执行“安全向下转型”,(运行成本大:普遍的实现版本基于“class名称之字符串比较”)

  • reinterpret_cast:执行低级转型,实际动作及结果可能取决于编译器,故而不可移植。

  • static_cast:强迫隐式转换

        dynamic_cast替代方案:使用虚函数:base class 内提供virtual函数做你想对各个派生类想做的事。(通过base class接口处理“所有可能之各种派生类”)

Item-28避免返回handles指向对象内部成分        避免返回“代表对象内部数据”的handle,将增加“对象的封装性”、帮助const成员函数更像个const,并将发生“dangling handles”的可能性降至最低。
Item-29为“异常安全”而努力是值得的

        异常安全函数即使发生异常也不会泄露资源或允许任何数据结构败坏。这样的函数区分为三种可能的保证基本型、强烈型、不抛异常型。“强烈保证”往往能通过copy-and-swap实现出来。

        copy-and-swap原则:为你打算修改的对象(原件)做出一份副本,然后在那副本身上做一切必要修改。若有任何修改动作抛出异常,原对象仍保持未改变状态。待所有改变都成功后,再将修改过的那个副本和原对象在一个不抛出异常的操作中置换 (swap)

Item-30透彻了解inlining的里里外外        将大多数inlining限制在小型、被频繁调用的函数身上。

        nlining函数:对此函数的每一个调用都以函数本体替换之。

  • 减少函数调用的开销

  • 编译器最优化机制通常被设计用来浓缩那些“不含函数调用”的代码,所以当你inline某个函数,或许你的编译器就因此有能力对它(函数本体)执行语境相关最优化。

inline是个申请,编译器可以忽略:

  • 大部分编译器拒绝将太过复杂(例如带有循环或递归)的函数inlining,而所有对virtual函数的调用(除非是最平淡无奇的)也会使inlining落空。

  • 编译器通过不对“通过函数指针而进行的调用”实施inlining

  • 【virtual意味着等待,直到运行期才确定调用哪个函数;而inline意味着执行前,先将调用动作替换为被调用函数的本体】

  • 构造函数、析构函数往往是inlining的糟糕候选人。编译器在编译期间可能产生必要代码,放在构造函数和析构函数内,以保障构造或析构行为完整。

将函数声明为inline的冲击:

  • inline函数无法随着程序库的升级而升级。一旦inline函数发生改变,所有用到该inline函数的客户端必须重新编译;

  • 代码膨胀;导致额外的换页行为,降低指令高速缓存装置的击中率

Item-31将文件间的编译依存关系降至最低

        支持编译依存最小化的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是Handle Classes【pimpl idiom】和Interface Class。

  • 如果使用object references或object pointers可以完成任务就不要使用objects。如果定义某类型的objects,就要用到该类型的定义式。

  • 尽量以Class声明式替换Class 定义式。当你声明一个函数而它用到某个Class时,并不需要该Class的定义,纵使函数以by value方式传递该类型的参数或返回值。

  • 为声明式和定义式提供不同的头文件。

        C++提供关键字export,允许将template声明式和定义式分割于不同的文件内。(不幸的是支持export的编辑器非常少)。

继续与面向对象设计Item-32确定你的public继承塑模出is-a关系        public继承意味着is-a。适用于base class的每一件事情一定也适用于derived class,因为每一个derived class对象也都是一个base class。
Item-33避免掩饰继承而来的名称
  • derived class内的名称会遮掩base classes 内的名称,在public继承下从来没有人希望如此;

  • 为了让被遮掩的名称再见天日,可使用using声明式或转交函数。

  • 内层多用域的名称会遮掩外围作用域的名称:

    C++的名称遮掩规则(name-hiding rules):所做的唯一一件事情就是遮掩名称,并不关心类型信息、访问权限、是否virtual、函数参数个数及类型。

Item-34区分接口继承和实现继承
  • pure virtual函数:接口继承

  • impure virtual函数:接口继承、缺省实现继承

  • non-virtual函数:接口继承、强制实现继承

Item-35考虑virtual函数以外的其他选择

virtual函数替代方案

  • 使用non-virtual interface(NVI)手法:它以public non-virtual 成员函数包裹较低访问性(private或protected)的virtual函数。这是Template Method设计模式的一种特殊形式。

  • 将virtual函数替换为“函数指针成员变量”,这是Strategy设计模式的一种分解表现形式。

  • 以tr1::function成员变量替换virtual函数,因而允许使用任何可调用物(callable entity:函数指针、函数对象、成员函数指针)搭配一个兼容于需求的签名式。这也是Stategy设计模式的某种形式。

  • 古典的Stategy模式:将继承体系内的virtual函数替换为另一个继承体系内的virtual函数,这也是Stategy设计模式的传统实现手法。

Item-36绝不重新定义继承而来的non-virtual函数
  • non-virtual函数都是静态绑定;由于pB被定义为pointer-to-B,通过pB调用的non-virtual函数永远是B所定义的版本。

  • 条款7:多态性base class内的析构函数应该是virtual,如果你违反了那个准则(在polymorphic base class内声明一个non-virtual析构函数),也就违反了本准则:因为derived class绝不应该重定义一个继承而来的non-virtual函数(此处指的是base class的析构函数)。

Item-37绝不重新定义继承而来的(virtual)缺省参数值
  • 绝不重新定义继承而来的non-virtual函数;
  • virtual函数是动态绑定,缺省参数值是静态绑定
Item-38通过复合塑模出has-a或“根据某物实现出”
  • 在应用域,复合意味has-a*(有一个);

  • 在实现域,复合意味着is-implemented-in-terms-of(根据某物实现出)

  • 在程序员之间复合(composition)这个术语有许多同义词:layering(分层)、containment(内含)、aggregation(聚合)和embedding(内嵌)。

Item-39明智而审慎的使用private继承

        private继承意味implemented-in-terms-of(根据某物实现出)。如果你让Class D以Private形式继承Class B,用意是为了采用class B内已经备妥的某些特性,而不是因为B对象和D对象存在任何观念上的关系。private继承意味只有实现部分被继承,接口部分应略去。

        尽可能使用复合,必要时才使用private继承(protected成员或virtual函数牵扯进来时)。

private继承主要用于“当一个意欲成为derived class者想访问一个意欲成为base class者的protected成分,或为了重新定义一个或多个virtual函数”。

        C++裁定凡是独立(非附属)对象都必须有非零大小。【EBO:empty base optimization】

Item-40明智而审慎的使用多重继承
  • MI:可能导致歧义、对virtual继续的需要。
  • C++解析(resolving)重载函数调用的规则:在看到是否有个函数可取用之前,首先确认这个函数对此调用是最佳匹配,找出最佳匹配函数后才检验其可取用性。
  • virtual继承:使用virtual继承的那些classes所产生的对象往往比使用non-virtual继承的兄弟们体积大;访问virtual base classes 的成员变量时也比访问non-virtual base classes 的成员变量速度慢。 virtual base classes初始化的规则更复杂(virtual base的初始化责任是由继承体系中的最低层 class负责)。
模板与泛型编程Item-41了解隐式接口和编译期多态

        面向对象编程:总是以显式接口和运行期多态解决问题

        Templates及泛型编程:隐式接口和编译期多态 重要性更高

        编译期多态:以不同的template参数具现化function templates,会导致调用不同的函数,这就是所谓的编译期多态。

Item-42了解typename的双重意义

        template 内出现的名称如果相依于某个 template 参数,称之为从属名称 (dependent names) 。如果从属名称在 class 内呈嵌套状,我们称它为嵌套从属名称 (nested dependent name)。C::const_iterator 就是这样一个名称。实际上它还是个嵌套从属类型名称 (nested dependent type name) ,也就是个嵌套从属名称并且指涉某类型。

        C++解析规则:如果解析器在template中遭遇一个嵌套从属名称,它便假设这个名称不是个类型,除非明确告诉它是(关键字typename)。

        typename只被用来验明嵌套从属名称;其他名称不应该有它存在。"

        “typename 必须作为嵌套从属类型名称的前缀词”这一规则的例外:typename不可以出现在base classes list内的嵌套从属类型名称之前,也不可在member initialization(成员初值列)中作为base class 修饰符。

        请使用关键字 typename 标识嵌套从属类型名称;但不得在 base class lists (基类列)或 member initialization list(成员初值列)内以它作为 base class 修饰符。

        typename 相关规则在不同的编译器上有不同的实践。这意味 typename和“嵌套从属类型名称"之间的互动,也许会在移植性方面带给你某种温和的头疼。

Item-43学习处理模板化基类内的名称

C++拒绝在templatized base classes(模板化基类)内寻找继承而来的名称,除非:

  • base class函数调用动作之前加上“this->”

  • using声明

  • 明确的“base classes 资格修饰符”(base::)

Item-44将与参数无关的代码抽离templates

        因非类型模板参数 (non-type template parameters) 而造成的代码膨胀,往往可

消除,做法是以函数参数或 class 成员变最替换 template 参数。

        因类型参数 (type parameters) 而造成的代码膨胀,往往可降低,做法是让带有

完全相同二进制表述 (binary representations) 的具现类型 (instantiation types) 

共享实现码。

         working set:所谓 working set 是指对一个在“虚内存环境”下执行的进程 (process) 而言,其所使用的那一组内存页 (pages)。

Item-45运用成员函数模板接受所有兼容类型
  • 必须同时声明泛化copy构造函数和正常的copy构造函数。(同样适用与assignment)
  • 请使用成员函数模板(member function templates)生成“可接受所有兼容类型”的函数。
Item-46需要类型转换时请为模板定义非成员函数

        function template实参推导过程中并不考虑采纳“通过构造函数而发生的隐式类型转换”。

        在一个class template内,template名称可以被用来作为“template和其参数”的简略表达式。

        当我们编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“Class template内部的friend函数”。

  1. 为了让类型转换可能发生在所有实参上,我们需要一个non-member函数

  2. 为了让这个函数被自动具现化,我们需要将它声明在class 内部

  3. 在class内部声明non-member函数的唯一方法:让它成为一个friend

Item-47请使用traits classes表现类型信息

        traits:允许在编译期间取得某些类型信息。他们以templates和“templates 特化”完成实现。

        整合重载技术后(overloading)后,traits classes有可能在编译期间对类型执行if...else测试:

  • 建立一组重载函数(身份像劳工)或函数模板(例如doAdvance),彼此的差异只在于各自的traits参数

  • 建立一个控制函数(身份像工头)或函数模板(例如advance),它调用上述那些劳工函数并传递traits classes所提供的信息。

Item-48认识template元编程
  • 模板元(TMP,Template metaprogramming)程序:是以C++写成,执行与C++编译器内的程序。
  • 由于TMP执行于C++编译期间,因此可将工作从运行期转移到编译期。这导致的结果:某些错误原本通常在运行期才能侦测到,现在可在编译期发现。
  • 较小的可执行文件、较短的运行期、较少的内存需求。编译时间变长。
  • 编译器必须确保所有源码都有效,纵使是不会被执行的代码。
定制new和deleteItem-49了解new-handler的行为

        当operator new抛出异常以反映一个未满足的内存需求之前,它会先调用一个客户指定的错误处理函数(new-handler)。为了指定这个用以“处理内存不足”的函数,客户必须调用set new_handler。

        new-handler,是当operator new无法满足客户的内存需求时所调用的函数。

        一个设计良好的new-handler必须做以下事情:

  • 让更多内存可被使用。

  • 安装另一个new-handler。

  • 卸载new-handler。

  • 抛出bad_alloc(或派生自bad_alloc)的异常。这样的异常不会被operator new捕捉,因此会被传播到内存索求处。

  • 不返回。【通常调用abort或exit】

        Nothrow new是一个颇为局限的工具,因为它只适用于内存分配(operator new),后续的构造函数调用还是可能抛出异常。

        注意:STL容器所使用的heap内存是由容器所拥有的分配器对象(allocator objects)管理,不是被new和delete直接管理。

Item-50了解new和delete的合理替换时机        operator new 应该内含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就该调用new-handler;

        它也应该有能力处理0byte 申请;

        Class专属版本则还应该处理比正确大小更大的(错误)申请,错误发生时改用标准operator new。

        operator delete应该在收到null指针时不做任何事情;

        Class专属版本则还应该处理“比正确大小更大的(错误申请)”,错误发生时改用标准operator 

        非附属/独立式对象:不以“某对象之base class成分”存在的对象。其大小必须非零。

Item-51编写new和delete时需固守常规-
Item-52placement new和placement delete要成对使用

        如果operator new接受的参数除了一定会有的那个size_t之外还有其他,这便是个所谓的placement new。

        伴随placement new调用而触发的构造函数“出现异常时:运行期系统寻找“参数个数和类型都与operator new相同”的某个operator delete,如果找到则调用,否则什么都不做。placement delete只有在“伴随placement new调用而触发的构造函数“出现异常时才会被调用。对一个指针施行delete绝不会导致调用placement delete。

  • 当你写一个placement operator new时,请确保也写出了对应的placement operator delete。否则可能发生内存泄露。

  • 当你声明placement new和placement delete,请确定不要无意识的掩饰了它们的正常版本。

杂项Item-53不要轻易忽略编译器的警告-
Item-54让自己熟悉包括RT1在内的标准程序库

Technical Report 1:

  • 智能指针:tr1::shared_ptr、tr1::weak_ptr。

       weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个由shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr,不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放。它是一种弱引用。

        weak_ptr常用函数:      

  • weak_ptr<T> w  空weak_ptr可以指向类型为T的对象
  • weak_ptr<T> w(sp)  与shared_ptr sp 指向相同对象的weak_ptr。T必须能转换为sp指向的类型
  • w  = p ,p可以是一个shared_ptr或者weak_ptr,赋值后w与p共享对象
  • w.reset()  将w置为空
  • w.use_count()  与w共享对象的的shared_ptr的数量
  • w.expired() 若w.use_count()为0,返回true,否则返回false
  • w.lock() 如果expired为true,返回一个空shared_ptr;否则返回一个指向w的对象的shared_ptr

  • tr1::function:表示任何callable entity(可调用物,也就是任何函数或函数对象),只要其签名符合目标。

  • tr1::bind

  • Hash tables:用来实现set,multi-sets,maps,multi-maps

  • 正则表达式:

  • Tuples(变量组):tr1::tuple 可持有任意个数的对象

  • tr1::array:本质上是个STL化数组,即一个支持成员函数如begin和end的数组。不过tr1::array的大小固定,并不使用动态内存。

  • tr1::mem_fn:这是个语句构造上与成员函数指针(member function pointers)一致的东西。

  • tr1::reference_wrapper:一个让references 的行为更像对象 的设施。它可以造成容器"犹如持有references"。而你知道容器实际上只能持有对象或指针。

  • 随机数生成工具

  • 数学特殊函数:

  • C99兼容扩充

  • Type traits:用以提供类型(types)的编译器信息。是否是个内置类型?是否提供virtual析构函数?是否是个empty class?是否可隐式转换为其他类型?

  • tr1::result_of:是个template,用来推导函数调用的返回类型。

        TR1自身只是一份规范。为了获得TR1提供的好处,你需要一份实物。一个好的实物来源是Boost。

        所有Boost组件都位于命名空间boost内,但TR1组件都置于std::tr1内,你可以这样告诉编译器,令它对待references to std::tr1就像对待references to boost一样。

namespace std {

    namespace tr1=::boost; //定义别名

}

Item-55让自己熟悉Boost-

参考博客:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值