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

章节 标号 条款 细节
让自己习惯C++ Item-1 视C++为一个语言联邦
Item-2 尽量以const、enum、inline替换#define
Item-3 尽可能使用const
Item-4 确定对象被使用前已先被初始化
构造/析构/赋值 Item-5 了解C++默默编写并调用那些函数
Item-6 若不想使用编译器自动生成的函数,则明确拒绝
Item-7 为多态基类声明virtual析构函数
Item-8 别让异常逃离析构函数
Item-9 绝不在构造和析构过程中调用virtual函数
Item-10 令operator=返回一个reference to *this
Item-11 在operator=中处理“自我赋值”
Item-12 复制对象时务忘其中每一个成分
资源管理 Item-13 以对象管理资源
Item-14 在资源管理类中小心copying行为
Item-15 在资源管理类中提供对原始资源的访问
Item-16 成对使用new和delete时要采取相同形式
Item-17 独立语句将newed对象置入智能指针
设计与声明 Item-18 让接口容易被正确使用,不易被误用
Item-19 设计class犹如设计type
Item-20 宁以pass-by-reference-to-const替换pass-by-value
Item-21 必须返回对象时,别妄想返回其reference
Item-22 将成员变量声明为private
Item-23 宁以non-member、non-friend替换member函数
Item-24 若所有参数皆需要类型转换,请为此采用non-member函数 唯有非成员函数才有能力“在所有实参身上实施隐式类型转换”
Item-25 考虑写出一个不抛出异常的swap函数
实现 Item-26 尽可能延后变量定义式的出现时间
Item-27 尽量少做转型动作
Item-28 避免返回handles指向对象内部成分
Item-29 为“异常安全”而努力是值得的
Item-30 透彻了解inlining的里里外外
Item-31 将文件间的编译依存关系降至最低
继续与面向对象设计 Item-32 确定你的public继承塑模出is-a关系
Item-33 避免掩饰继承而来的名称
Item-34 区分接口继承和实现继承
Item-35 考虑virtual函数以外的其他选择
Item-36 绝不重新定义继承而来的non-virtual函数
Item-37 绝不重新定义继承而来的(virtual)缺省参数值

绝不重新定义继承而来的non-virtual函数;

virtual函数是动态绑定,缺省参数值是静态绑定

Item-38 通过复合塑模出has-a或“根据某物实现出”
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的双重意义

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

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

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

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和delete Item-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的合理替换时机
Item-51 编写new和delete时需固守常规
Item-52 placement 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

参考博客:

猜你喜欢

转载自blog.csdn.net/qfturauyls/article/details/129768563
今日推荐