C++ – Effective C++

目录

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

C++分为:C部分、对象C++部分、模板C++部分、STL部分。

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

const: 表示修饰的内容不可更改。

enum: 本质是 int 类型。

inline: 在 class 内时,如果函数允许,他被自动启用。

条款 03:尽可能使用 const

这有利于编译器更好的优化程序,并且让客户减少误操作。

在函数传值时,对于自定义类型,最好使用 const 引用。

用 mutable 修饰成员变量,表示允许成员 const 方法改变该变量。

为避免代码重复,请用非 const 函数调用 const 函数,而不是颠倒。

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

在 class 的构造函数表中显式写出所有成员函数,且按照声明次序。

成员变量的初始化次序与声明次序相同。

如果有变量被用于声明全局资源:

class FileSystem {
    // TODO: ...
};
extern FileSystem tfs;

为了保证构造次序正确,请放到工厂函数中:

FileSystem & tfs() {
    static FileSystem fs;
    return fs;
}

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

class Empty {
pubilc:
    Empty() { ... }
    Empty(const Empty & rhs) { ... }
    Empty(const Empty && rhs) { ... }
    ~Empty() { ... }

    Empty & operator = (const Empty & rhs) { ... }  
    Empty & operator = (const Empty && rhs) { ... }
};

以上是全部的默认函数,最好全部自己编写他们。

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

如题,在函数后面使用 = delete 删除默认函数。

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

使用指向具有非 virtual 析构函数 的基类的指针来删除派生类对象会导致未定义的行为。为了纠正这种情况,应该用 virtual 析构函数来定义基类。

使基类析构函数 virtual 保证派生类的对象被正确地析构。

任何时候在类中都有一个 virtual 函数,您应该立即添加一个 virtual 析构函数。

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

解构函数不可抛出异常,如果一定有异常,就要直接结束程序。

或者将其异常部分放在另一个函数中,允许用户提前以其他方式调用。

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

条款 10:令 operator = 返回一个 *this 的引用

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

首先为 rhs 数据创建副本,然后交换副本。

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

在 copy 函数中调用基类的 copy 函数,构造 copy 时。

不要因两个 copy 函数中有重复代码就让其中一个调用另一个,应放在第三个函数中。

条款 13:以对象管理资源

资源指类似 GLFW 的窗口一样的东西,在 create 后,需要用特定函数 delete 。

如果不是用对象管理,你的客户可能会忘掉 delete 。

std::auto_ptr:智能指针,其复制函数会把复制方置为 nullptr 。

不允许多个对象持有同一个指针。

std::tr1::shared_ptr:同上,不过允许一个指针被多个对象持有。

他在最后一个对象销毁时销毁指针,允许指定特定的 delete 函数。

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

这可能导致资源在使用时已经被销毁。

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

返回原始数据的指针。

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

条款 17:以独立的语句将 newed 对象植入智能指针

防止出现异常时,出现内存泄漏。

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

让 class 的行为尽量与 int 和 STL 相似。

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

  • 新 type 的对象如何创建与销毁
  • 对象的初始化和赋值有什么区别
  • type 如果被以值传递会发生什么
  • 什么是 type 的合法值
  • 需要配合某个继承图系吗
  • type 需要什么样的转换
  • 应该有哪些操作符和函数合法
  • 哪些编译器自动生成的函数需要驳回
  • 谁该是新 type 的成员
  • 什么是新 type 的 “未声明接口”
  • 需要模板化吗

条款 20:宁以 const 引用传参替换以值传递

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

直接在 return 语句中使用构造,创建一个新的对象。

条款 23:宁以 非成员 或 非友元 函数替换 成员函数

条款 24:若所有参数皆需要类型转换,请为此采用 非成员 函数

使用成员函数重载 operator * 时,如果 * 的左端可能需要转换,请使用非成员函数。

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

例如在 pimpl 手法中只需要交换指针。

最好将 std::swap 偏特化,便于客户使用。

如果无法偏特化(比如模板 class),也不要考虑为 std 加入重载函数。

在 class 的命名空间中放置特殊的 swap 函数是不错的。

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

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

C 转型为 类型名+变量名 例如:(int)x 或 int(x) 。

C++ 提供的新的转型方案:

const_cast:可以移除常量性。

dynamic_cast:确保安全的先下转型。(可能带来更多资源消耗)

reinterpret_cast:低级转型,比如把 int指针 转换为 int 。

static_cast:强迫隐式转型,和 C 转型大致相同。

少用 C 转型!

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

handles:像是非 const 引用和指针一样可以改变内部数据的东西。

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

基本承诺:异常抛出后,程序内的任何事物依然保持有效状态。

但是无法确定有效状态是函数执行前的状态,还是恢复到默认缺省状态。

强烈保证:如果函数失败,程序回到函数调用前。

无异常保证:函数不会失败,用 noexcept 修饰。或是失败对程序是致命的。

强烈保证的实现方法可以是,先处理一个副本,然后与原始数据 swap 。

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

在 class 声明中定义的函数,常常是 inline 的。

在 class 中的空函数不一定真的为空,这时不要使用 inline 。

(他可能调用了父类及成员的函数)

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

尽量使用前置 class 声明。

这会导致成员变量问题,可以用 pimpl idiom 解决。

如果可能,尽量用引用和指针代替对象本体。

如果可能,尽量用 class 声明代替 class 定义。

为声明式和定义式创建不同的头文件。(以 fwd 作为头文件后缀)

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

即每一个子类也是基类。(每一个学生(子类)是人(基类))

你需要想想,正方形是矩形吗?矩形改变高度的属性继承给正方形会发生什么?

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

遮掩就是一个作用域内的变量可以屏蔽同名全局(外作用域)的变量。

子类的函数会遮掩父类的函数。

如果想继承父类函数,仅仅只是想重载,请在子类中使用 using 引用。

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

成员函数接口一定被继承,父类可做的事,子类也必须可做。

可以为纯虚函数建立定义,但在调用时需要明确标出作用域名。

纯虚函数继承定义。

虚函数继承定义和缺省实现。

非虚函数继承定义和强制性实现。

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

Non-Virtual Interface 的 Template Method 模式:让一个非虚成员函数调用虚的内部虚函数,方法在调用虚函数前进行事前处理,比如互斥锁等等。

Function Pointer 的 Strategy 模式:类似 GLFW 的回调函数,采用绑定,在函数构造中绑定函数。

tr1::function 的 Strategy 模式:tr1::function 可以自动兼容函数指针。

传统的 Strategy 模式:将虚函数放入另一个继承体系。

条款 36:绝不重新定义继承而来的非虚函数

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

但是缺省参数值是静态绑定,所以最好让静态函数调用内部虚函数,实现动态绑定。

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

复合:在 class 中放置一个 class 。

成员与 class 的关系通常是 has-a (有一个)的。

根据某物实现出通常用于使用另一个 class 的代码,但不是 is-a 关系。

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

private 继承不存在继承关系,即不是 is-a 关系。

他是 根据某物实现出 关系的。

private 继承意味着父类中的 public 成员将变成 private 。

通常用于接口。private 继承可以被复合代替。

EBO:一个空 class 如果当做成员也占用空间,但是继承不会。

(空类不是真的空,他通常拥有静态对象)

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

如何多重继承中有基类成员重名,需要明写所在空间。

virtual public:虚继承,会降低程序效率,但可避免继承一个 class 多次。

多重继承常用于类似 java 的接口的功能。

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

不同的模板在编译期被确定调用那些函数。

class 的接口是显示的,可以在定义式中看到,模板则不是。

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

template<class T> class Widget;
template<typename T> class Widget;

以上两个式子并没有去别,但最好使用 class 。

typename C::const_iterator it;

typename 可以用来确定其后面的表达式是一个类型,而不是函数等等。

typename 必须做为嵌套从属类型的前缀,不能在基类表列和初始化表列存在。

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

因为模板基类可以被特例化,所以即使继承了基类也无法使用其成员。

但是可以通过定义资格修饰符来假设他有。或使用 this 指针。

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

使用模板可能导致二进制码膨胀。

以 class 成员代替模板参数消除膨胀。

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

为模板 class 制作一个泛化 copy 函数。

即使你制作一个了泛化 copy 函数,但是你的程序还是缺省 copy 函数。

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

以 class 模板内部的友元函数定义。

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

这个技术用来确定类型是否合法(如是否是内置类型),在编译期给出警告。

迭代器:

input:只能向前移动,而且只读,读仅一次。

output:只能向前移动,而且只写,写仅一次。

forward:可向前向后移动,可读写,在一些实现中可能为单向。

bidirectional:可向前向后移动,可读写,例如 set 的。

random access:可向前向后任意长度,可读写。例如 vector 的。

traits classes:

重载函数:差别仅仅是 traits 参数,用来区分调用不同的函数。

控制函数:调用重载函数。

条款 48:认识模板元编程

模板元编程是在编译期执行的程序。

下面是 TMP 的一个例子,他可以在编译期获得阶乘:

template<unsigned int n>
struct Factorial {
    enum { value = n * Factorial<n - 1>::value };
};
template<>
struct Factorial<0> {
    enum { value = 1 };
};

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

new-handler 是在 new 时发现空间不足时调用的提示函数。

他不接受任何参数也不返回任何参数。

可以使用 set_new_handler 函数来设置自定义函数,该函数返回上一个 new-handler 函数。

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

  • 用来检测运用上的错误。
  • 为了收集使用时的统计数据。
  • 增加运行速度。
  • 减少空间使用。
  • 弥补缺省的分配器的非最佳齐位。
  • 让对象成簇放置。
  • 为了获得非传统行为。

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

  • 处理 0-byte 申请。(视其为 1-byte 申请)
  • 确保不会出现内存泄漏。
  • 为 class 特制时,如果申请大小不符,自动调用默认函数。
  • new 中应该有一个 while(true)
  • 妥善处理 nullptr

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

这些函数用于在特定的位置申请内存。

请不要无意识的使其掩盖正常版本的分配器。

条款 53:不要轻视编译期警告

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

TR1 自身只是一份规范,主要实现来着 Boost 。

组件实例:

  • 智能指针 – tr1::shared_ptr 和 tr1::weak_ptr:后者可以处理环。
  • tr1::function:表示所有可调用体,类似函数指针。
  • tr1::bind:STL绑定器。

独立机能部分:

  • Hash tables:用来实现容器。
  • 正则表达式。
  • Tuples:变量组,类似 pair ,但可以持有更多变量。
  • tr1::array:数组。
  • tr1::mem_fn:成员函数指针。
  • tr1::reference_wrapper:让引用的行为更像对象。
  • 随机数。
  • 数学库。
  • C99扩充。

模板编程部分:

  • Type traits:用于判断是否是合法类型。
  • tr1::result_of:用来推导函数返回值类型。

条款 55:让自己熟悉 Boost

boost.org

发表回复