effective c++

from effective c++

理解C++

(1) c++是一个语言联邦 C Object-oriented c++ Template c++ STL

(2) 尽量使用const enum inline 替换 #define 宁可以编译器替换预处理器

#define RATIO 1.653
宏定义的常量标记RATIO 没有进入记号表 盲目使用宏定义会使得目标码出现多份 1.653

宏定义无法定义class专属常量

形似函数宏定义容易出错

单纯常量 使用const或enum 替换 #define
形似函数 使用inline替换#define

(3) 尽可能使用const

顶层const 底层const

星号左边 被指物是常量 星号右边 指针本身是常量

某些东西被声明为const可以帮助编译器侦测出错误用法 const可被任何作用域的对象 函数参数 函数返回类型 成员函数本体

编译器强制实施 bitwise constness .编写程序时应该使用“概念上的常量性” 即 const成员函数不能更改对象中任何non-static成员变量

mutable能释放掉non-static成员变量的bitwise constness

当const和non-const成员函数有着实质等价实现时 令non-const版本调用const版本能够避免重复代码 使用转型(const_cast<> static_cast<>)

(4) 确定对象使用前已被初始化

c++规定对象成员变量的初始化动作发生在进入构造函数本体之前 可以使用成员初始列

调用一次copy构造函数 比一个构造和一个copy赋值高效

c++ 有十分固定的“成员初始化次序” 和定义次序一致

函数内static对象被称为 local static 对象 其他被称为 non-local static 对象 程序结束时static会被自动销毁 也就是它们的析构函数在main函数结束时被自动调用

c++对不同编译单元内的non local static对象 的初始化次序并未明确定义 将non-local static对象搬到自己的专属函数内(单例模式) non-local static 被 local static 对象替换

构造 析构 赋值运算

(5) 了解c++调用哪些函数 默认构造函数 拷贝构造 赋值 析构函数

定义reference或const变量 没有定义赋值操作 c++的响应是拒绝编译那一行的赋值动作。

(6) 若不想使用编译器自动生成的函数 明确拒绝: 将相应的成员函数声明为private并不予实现。

(7) 为多态基类声明virtual析构函数

基类指针指向派生类 删除基类指针 “局部销毁” 派生类自身内容未被删除 造成内存泄漏

classes的设计不是作为基类使用 不应该声明virtual析构函数

(8) 别让异常逃离析构函数

假设vector中存了多个对象 对象能够在析构函数中抛出异常 当调用这些对象析构函数 出现多个异常 会使得程序终止或产生未定义的行为

析构函数调用的函数可能出现异常 那么析构函数应该捕捉异常 吞下它或终止程序

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

(9) 在构造和析构期间不要调用virtual函数

(10) 令 赋值操作(operator=) 返回一个 reference to *this

(11) 在operator= 中处理“自我赋值”

确保对象自我赋值时operator= 有良好的行为。 其中包括比较“来源对象” 和目标对象的地址、 精心周到的语句顺序 、 copy and swap

class Bitmap{};
class Widget{
public:...
private:
    Bitmap *pb;};

证同测试

Widget & Widget::operator =(const Widget& rhs){
  if(this == &rhs) return *this;
  delete pb;
  pb = new Bitmap(*rhs.pb);
return *this;}

精心周到的语句顺序

Widget & Widget::operator =(const Widget& rhs){
  Bitmap *orign_pb = pb;
  pb = new Bitmap(*rhs.pb);   // 为 rhs 创造一份副本
  delete orign_pb;
return *this;}

copy and swap

Widget & Widget::operator =(const Widget& rhs){
  Widget temp(rhs);    
  swap(temp);
  return *this;}

(12) 赋值对象时勿忘其每一个成分

copy函数应该确保 复制所有的local变量, 在定义derived class定义copy函数时 调用适当的base class 中的copy函数

资源管理

(13) 以对象管理资源

获得资源后立即放入对象管理
管理资源的对象通过折构函数确保资源的释放
auto_ptr 能实现动态分配对象的自动释放 要求其对裸指针的完全占有性 但另一个auto_ptr进行复制拷贝时 其失去所有权 为null
shared_ptr 智能指针 允许复制拷贝 引用计数

(14)在资源管理类中小心copy行为

面对不合理的资源复制行为: 禁止复制 使用shared_ptr引用计数

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

get方法提供显示转换 安全
operator 类型名 隐式转换

(16) 成对使用new和delete时 采用相同形式
new 使用[] delete也使用[]
new 不使用 delete也不使用

(17)以独立语句将新创建的资源对象置入智能指针

防止在创建资源和置入智能指针的时间点之间产生异常
造成资源泄漏

设计与声明

(18) 接口容易被正确使用 不易被误用

促进正确使用包括接口一致性 内置类型的行为兼容
阻止误用 消除客户的资源管理责任
使用shared_ptr 支持定制的删除器

(19) 像type一样设计class
设计class需要注意的地方:
创建和销毁
对象的初始化和赋值之间的区别
copy构造函数定义值传递
合法的新type对象
继承体系
类型转换 隐式转换 显式转换
操作符和函数对新type式合理的
什么样的标准函数应该驳回
谁使用新type的成员 private public的定义
未声明接口
一般化 template
这个新的type的需求是确切的吗?

(20) 使用pass-by-reference-to-const 替换 pass-by-value
使用pass-by-reference-to-const 替换 pass-by-value 更高效 避免切割问题
对于内置类型 如stl的迭代器和函数对象 pass-by-value比较合适

(21)该返回对象时返回对象

返回局部对象的引用或指针 因为原来对象已经销毁 会产生未知错误

标准做法:

const Rational operator *(Rational &lhs, Rational &rhs) {
   return Rational(lhs.numerator()* rhs.numerator(), lhs.denominator()*rhs.denominator());}

函数返回局部对象时做的操作

函数的局部对象在当前函数被调用的时候创建,存储在栈区,在函数结束以后就会被释放,如果存在返回值,那么当前对象也会被释放,只不过在被释放前做了一次拷贝,拷贝到接受该返回值的另外一个对象上面,所以函数的入参和返回值其实都进行的是拷贝操作,新的对象被赋值,旧的的对象被回收

(22)成员变量声明为private
private有很好的封装性 允许class作者有功能实现的弹性,保证客户访问数据的一致性
protected并不比public更具有封装性

(23) 使用non-member non-friend函数替换member函数
提供原始操作来的类 如浏览器类中的清除浏览器功能 包括清除缓存 历史记录等 这个功能不应该内置 由工具类提供更为合适

(24)为某个函数的所有参数提供类型转换 这个函数是个non-member
如 Rational的乘法操作

const Rational operator *(Rational &lhs, Rational &rhs) {
   return Rational(lhs.numerator()* rhs.numerator(), lhs.denominator()*rhs.denominator());}

比 member方法

const Rational operator *(const Rational &rhs) const{...}

更为合适

(25) swap函数注意事项
std:swap效率不高时 提供一个swap成员函数 确保不抛出异常
如何提供一个member函数 也该提供一个non-member swap调用前者
调用std::swap时 使用using声明

实现

(26) 延后变量定义式的出现 在需要使用变量时再定义。

(27) 尽量少做转型
const_cast(expression) 对象常量的消除

dynamic_cast(expression) “安全向下转型”
类层次结构中父类和子类之间指针和引用的转换

reinterpret_cast(expression) 执行低级转型 将pointer to int 转型为 int

static_cast(expression) 强迫隐式转换 non-const对象转换为const对象 int为double , pointer-to-base 转换为pointer-to-derived 用于基本数据类型之间的转换,把空指针转换成目标类型的空指针,把任何类型的表达式类型转换成void类型,用于类层次结构中父类和子类之间指针和引用的转换。

另外,C++中层次类型转换中无非两种:上行转换和下行转换

Base *P = new Derived();
Derived *pd1 = static_cast<Derived *>(P);
Derived *pd2 = dynamic_cast<Derived *>(P);

static_cast和dynamic_cast都能正确执行

Base *P = new Base;
Derived *pd3 = static_cast<Derived *>(P);
Derived *pd4 = dynamic_cast<Derived *>(P);

static_cast能够执行成功 不安全 dynamic_cast在运行时类型检查 转换不合理 返回NULL

explicit关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换。

避免转型 若pointer-to-base 指向derived时 需要使用derived的方法 可以替换的方案是 直接使用pointer-to-derived或在base类中定义virutal方法
使用新式转型

(28) 避免返回handles(包括references 指针 迭代器)指向对象内部 封装性 防止用户修改对象内部数据或产生未知错误

(29) 异常安全函数 即使发生异常亦不会泄露资源或允许任何数据结构的败坏

(30)理解inline

inline函数 以函数本体替换 程序体积大 额外的换页行为
inlining 是编译期行为

inline函数限制在小型 频繁调用的函数身上
不要应为 function templates出现在头文件 就将它们声明为inline

(31) 编译依存性最小化:相依于声明式 不要相依于定义式
两个手段 handles classes 和 interface

继承与面向对象设计

(32) public 继承关系应该是 is-a 关系

(33) 避免遮蔽继承而来的名称
derived classes内的名称会遮蔽 base classes内的名称
为了遮蔽名称再见天日 使用using声明式

(34) 区分接口继承和实现继承

具有纯虚函数的类时接口

纯虚函数只指定接口继承 derived类中需提供纯虚函数的实现 另外 通过base类名::纯虚函数方法 可以提供纯虚函数的缺省实现

非纯虚函数具体制定接口继承和缺省实现继承 若derived不重新定义 可以使用base的缺省实现

非虚函数制定接口继承和强制性实现继承

(35) 考虑virtual函数以外的其他选择

在classes定义内呈现成员函数的本体 就让它们暗自成了inline

通过public的非虚函数 调用private的虚函数 称为non-virtual-interface(nvi)手法 非虚函数称为虚函数的外覆盖器
能够在调用虚函数之前或之后做一些操作

使用nvi手法替换虚函数

strategy设计模式 使用函数指针变量替换虚函数 使用trl::function成员变量替换虚函数 继承体系中的虚函数替换为另一个继承体系内的虚函数

函数指针示例:

class GameCharater;
int defaultHealthCalc(const GameCharacter & gc);
class GameCharacter{
public:
    typedef std::trl::function<int (const GameCharacter&)> HealthCalcFunc;
    explicit GameCharacter(HealthCalcFunc hcf=defaultHealthCalc):healthFunc(hcf);
    int healthValue() const
    {
        return healthFunc(*this);
    }
private:
    HealthCalcFunc healthFunc;
}

传统strategy设计模式

(36) 绝对不要重新定义继承而来的非虚函数

(37) 绝对不要重新定义继承而来的缺省参数值
虚函数时动态绑定的 而缺省参数值却是静态绑定的 运行效率问题
使用nvi方法解决

(38) 复合关系与继承不同 意味着 has-a或 is-implemented-in-terms-of(根据某物实现)

(39) 明智而审慎地使用private继承

sizeof(空对象) 结果为1 c++官方勒令安插一个char到空对象内 字节对齐 不一定为1

private继承意味着 is-implemented-in-terms if。通常使用复合更合适。但derived需要访问 protected base的成员或重新定义virutal函数时 ,这种设计是合理的。

private继承可以造成empty base 最优化。

(40) 明智而审慎地使用多重继承

多重继承的父类有相同的函数 容易造成歧义 需要使用virtual继承

vitual继承增加大小 速度 初始化等成本。virtual base classes不带任何数据是比较好的选择

使用public继承某个接口 使用private继承某个协助实现的class,可以使用多重继承。

模板与泛型编程

(41) template 编译期多态

(42) 理解typename

在模板中表示类型参数时 typename与class 一致

在模板中涉及到嵌套从属类型名时 需要在它的前一个位置加上关键字typename 告诉编译器这是个类型 不允许出现在成员初值列
eg:

template<typename C>
void print2nd(const C& container)
{
    if(container.size() >= 2)
    {
        typename C::const_iterator iter(container.begin());
    }
}

(43) 处理模板化基类的名称

derived class temolates 对base classes的内容不了解,
可以通过this指向basic class template的成员变量 或使用using 或使用域名修饰符 base::method

(44)将参数无关的代码抽离template
共性与变性分析

代码膨胀 相同模板 产生两份代码

非类型参数造成的代码膨胀 以函数参数或class成员变量替换template参数

类型参数造成的代码膨胀 带有完全相同二进制表述的具现类型共享实现码。

(45)运用成员函数模板接受所有兼容类型

泛型编程
Top 基类 Middle继承Top Bottom继承Middle
希望实现 根据一个SmartPtr或一个SmartPtr构造出一个SmartPtr
eg:

template<typename T>
class SmartPtr{
public:
    template<typename U>
    SmartPtr(const SmartPtr<U>& other):heldPtr(other.get());
    T* get() const {return heldPtr;}
private:
    T *heldPtr; 
}

如果你声明member templates 用于泛化copy构造或泛化assignment操作 你还需要声明正常的copy构造函数和assignment操作符

eg:

template<class T>
class shared_ptr{
public:
    shared_ptr(const shared_ptr& r);
    template<class Y>
    shared_ptr(const shared_ptr<Y>& r);
    shared_ptr& operator=(shared_ptr const& r);
    template<class Y>
    shared_ptr operator=(const shared_ptr<Y>& r);
}

(46) 编写一个class template 它所提供的以template相关的函数支持“所有参数之隐式类型转换” ,请将函数定义为 clas template内部的friend函数

eg:

template<typename T> class Rational;
template<typename T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs)
{
    return Rational<T>(lhs.numerator()*rhs.numerator(), lhs.denominator()*rhs.denominator());
}

template<typename T>
class Rational
{
public:
    friend const Rational<T> operator*(const Rational<T>& lhs, Rational<T>& rhs)
    {
        return doMultiply(lhs, rhs);
    }
}

(47) traits class 表现类型信息

std::iterator_traits<IterT>::value_type tmp 

类型为IterT之对象所指mp之物的类型 如果IterT为vector::iteratro 则tmp为int

traits classes用以提供类型信息,如迭代器类型iterator_traits(如random_access_iterator_tag,bidirectional_iterator_tag,input_iterator_tag)

trait classes 使得类型相关信息在编译期可用

整合重载技术 根据不同类型信息 进行相关操作

(48) 元编程
template metaprogramming(TMP, 模板元编程) 是编写template-based C++ 程序并执行于编译期的过程。

TMP将工作由运行期转移编译期 得以实现早期错误侦测和更高的执行效率

一个通过递归模板具现化实现接触的TMP程序:

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

template<>
struct Factorial<0>{
    enum{ value = 1 };
}

定制new和delete

(49) 了解new-handler的行为

当 operator new抛出异常 它会调用一个客户制定的错误处理程序 就是new-handler

设计一个良好的new-handler: 让更多内存可被使用 安装另一个new-handler 卸载new-handler 抛出bad_alloc 不返回

operator new 执行过程:

nothrow new 只适用于内存分配 后继的构造函数调用还是可能抛出异常。

(50) 了解new和delete合理替换时机

new得到的内存 没有delete 造成内存泄露
new得到的内存 多次delete 导致不确定行为

定制 new和delete 改善性能 对heap错误进行调试 收集heap使用信息

(51) 编写new和delete 固守常规

operator new 应该包含一个无穷循环 如果它无法满足内存需求 就调用new-handler 。另外 它应该有能力处理0 bytes申请。Class专属版本应该处理“比正确大小更大(错误)申请”

operator delete应该在收到null指针时不做任何事情,Class专属版本应该处理“比正确大小更大(错误)申请”。

(52) placement new placement delete
placement new 意味着额外参数的new

使用placement new

Widget *pw  = new (std::cerr)Widget;

在构造函数时发生异常 若没有相应的placement delete 则会造成内存泄露

写一个placement operator new 应该确定对应的placement operator delete

声明placement new和placement delete 请不要无意识遮掩它们的正常版本。可以使用using 或 base clas中包含所有正常形式的new和delete

杂项讨论

(53) 不要忽视编译器的警告

(54) 熟悉标准程序库

(55) 熟悉 boost