《Effective C++:改善程序与设计的55个具体作法》 by Scott Meyers
明智选择+精心设计
软件设计:“令软件做出你希望它做的事情”的步骤和做法,所谓最佳设计,取决于系统希望做什么事。
解决一个设计问题的方法不止一种,要训练自己思考多种方法。
声明(declaration):告诉编译器某个东西的名称和类型,但略去细节。
定义(definition):提供过编译器一些声明所遗漏的细节。
切记将成员变量声明为 private。
The protected label gives derived classes access to the protected members of their constituent base-class objects, but keeps these elements inaccessible to users of the classes. from <Accelerated C++>
protected 和 public 成员变量一样缺乏封装性,如果成员变量被改变,都会有不可预知的大量代码受到破坏。
初始化(initiation):给予对象初值的过程。C++ 规定:对象成员变量的初始化在进入构造函数本体之前,早于赋值。好的初始化方式(效率高)是使用成员初始化列表(member initialization list)。构造函数本体不必有任何动作。
初始化顺序:base class 早于 derived class,class 的成员变量总是以其声明次序被初始化。
不同编译单元(源文件)内的 non-local static 对象的初始化顺序属于未定义行为。
default 构造函数:构造函数没有参数 or 每个参数都有缺省值。
explicit 构造函数:禁止编译器执行非预期的类型转换(隐式类型转换)。
copy 构造函数:用来以同类型对象初始化自我对象。copy assignment 操作符:用来从另一个同类型对象中拷贝其值到自我对象。
class Widget { public: Widget(); // default 构造函数 Widget(const Widget& rhs); // copy 构造函数 Widget& operator=(const Widget& rhs); // copy assignment 操作符 ... }; Widget w1; // 调用 default 构造函数 Widget w2(w1); // 调用 copy 构造函数 w1 = w2; // 调用 copy assignment 操作符(无新对象被定义) Widget w3 = w2; // 调用 copy 构造函数!!! // 有新对象被定义(如w3),一定有个构造函数被调用,所以不可能为赋值操作
passed-by-value:意味着“调用 copy 构造函数”。
使用 passed-by-reference-to-const。效率高(避免了对象创建)、避免了对象切割问题。
const char *p; // 常量数据 char *const p; // 常量指针 const char *const p; // 常量指针 & 常量数据
引用与指针:
- 窥视 C++ 编译器源码,你会发现: reference 以指针实现出来
- reference 不能为 null
- reference 必须被初始化
- reference 相较 pointer 效率高,因为使用 reference 之前不需要测试其有效性(使用 pointer 通常得测试它是否为 null)
- pointer 可被重新赋值,reference 总是指向(代表)它最初获得的对象(“一旦代表了该对象就不能够再改变”)
mutable:去除成员变量的 const 属性。被 mutable 修饰的成员变量可能总是会被修改,即使在 const 成员函数内。
delete:禁止默认拷贝构造函数(copy 构造函数)和赋值操作(copy assignment 操作符)。使用 private 限定符,亦可以达到这个目的(“将成员函数声明为 private 而且故意不实现它们”)。如:
class ios_base { private: ios_base(const ios_base&); ios_base& operator=(const ios_base&); ... };
如果为空,编译器为你写函数:
- default 构造函数(在没有声明任何构造函数的情况下)
- copy 构造函数
- 析构函数
- copy assignment 操作符
因此,如果你的类:
class Empty { };
最终是等于写下:
class Empty { public: Empty() { ... } //default 构造函数 Empty(const Empty& rhs) { ... } //copy 构造函数 ~Empty() { ... } //析构函数 Empty& operator=(const Empty& rhs) { ... } //copy assignment 操作符 };
局部销毁:当 derived class 对象经由一个 base class 指针被删除,而 derived class 的析构函数是 non-virtual 的,(结果未定义) 通常情况是 derived 部分没被销毁(隐含的就是资源泄漏)。解决:为多态基类声明 virtual 析构函数。正确做法如:
class cbase { public: cbase(); virtual ~cbase(); // !!! ... }; cbase *pc = new Derived(); ... delete pc; // correct
另:任何 class 只要带有 virtual 函数都几乎确定有一个 virtual 析构函数。
- 绝不在析构函数中抛出异常。最好任何情况下都不使用异常。
- 绝不在构造函数和析构函数中调用 virtual 函数。
- 令赋值(assignment)操作符返回一个 reference to *this && 避免自我赋值,示例:
Widget& operator=(const Widget& rhs) { if (this == &rhs) // 防止自我赋值 return *this; ... return *this; }
- 为防止资源泄露,用对象管理,在构造函数中获得资源并在析构函数中释放资源。
- 好的接口可以防止无效的代码通过编译。
- 如果成员函数是个 non-virtual 函数,意味着它并不打算在 derived class 中有不同行为,所以绝不该在 derived class 中被重新定义。
类型转换:
- const_cast<T>(expression):通常被用来将对象的常量性删除(cast away the constness, const->non-const)。
- dynamic_cast<T>(expression):继承关系的转型,用来执行“安全的向下转型”,执行速度相当慢(从编译器的角度,通过 strcmp 比较、确认不同的类)。
- reinterpret_cast<T>(expression):低级转型,其转换结果几乎总是与编译平台息息相关,故不具移植性,如 pointer to int 转型为一个 int。
- static_cast<T>(expression):用来强迫隐式转换,基本上拥有与 C 旧式转型相同的威力与意义,以及相同的限制。
2020.7.10