Effective C++ 一些记录和思考 (2)

全特化版本,与 STL 保持一致性,这样 Widget 对象就可以正常的调用 swap 了
`cpp class Widget { public: void swap(Widget& other) { using std::swap; swap(pImpl, other.pImpl); } private: WidgetImpl *pImpl; }; namespace std { // std template <> void swap<Widget> (Widget& a, Widget& b) { a.swap(b); } }

偏特化一个 function template 时,惯用手法是简单的为它添加一个重载版本
cpp namespace WidgetFpp { // 这里不是添加到 std 中了 class WidgetImpl {}; class Widget {}; template<typename T> void swap(Widget<T>& a, Widget<T>& b) { a.swap(b); } }

一般经验

能使用 std::swap 就不去造轮子

有 pimpl 手法的,或类似的

提供一个 public swap,置换该类型的两个对象

在当前 class 或者 template 所在的命名空间内提供一个 non-member swap,并调用上述成员函数

如果当前是 class 而不是 class template,为class特化 std::swap,并调用 swap 成员函数

在调用 swap 前,使用 using std::swap, 自动匹配合适的那个 swap 函数 实现

Item 26 - 尽可能延后变量定义是的出现时间

以“具明显意义之初值”将变量初始化,还可以附带说明变量的目的

Item 27 - 尽量少做转型动作

书中对 Window::OnResize() 的分析,这里用过代码看结果,L18 这里转型后调用的OnResize()的*this是一个副本,L19 直接调用基类 OnResize能够得到正确的结果
cpp 7 class Window { 8 public: 9 virtual void OnResize() { 10 a = 3; 11 } 12 int a; 13 }; 14 15 class SpWindow : public Window { 16 public: 17 void OnResize() override { // C++ 11 18 // static_cast<Window>(*this).OnResize(); // 这里 a = 0 19 Window::OnResize(); // a = 3 20 } 21 };

谨慎使用 dynamic_cast

Item 28 - 避免返回handles指向对象内部成分

书中的例子会造成 bitwise constness, 见 Item 3, 但是 clang 3.8 上这些代码已经不能编译通过了

Item 29 - 值得为“异常安全”花费精力

异常安全的要求

不泄露任何资源

不允许数据败坏

资源泄漏用对象管理,如锁

单个函数的安全保证,copy-and-swap

多个函数“连带效应”, 对每一个函数都实现安全保证

Item 30 - 了解 inline

inline 和 template 通常被定义于头文件内。 是因为通常而言,inlining 和 template 是编译期的行为,编译器必须知道被调用函数的本体

改变程序需要重新编译,而不能像普通的函数直接链接即可

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

Handle class 和 Interface class 解除接口和实现之间的耦合关系,降低文件间的编译依赖

Item 32 - 避免遮掩继承而来的名称

derived class 继承了 base class 内的所有东西,实际上的运行方式是 derived class 作用域被嵌套在 base class 作用域内 继承与面向对象设计

Item 33 - 确认 public 继承塑造出 is-a 关系

public 继承适用于 base class 对象上的每件事情也能够作用于 derived class 对象上

base class 和 derived class 中有相同的函数名称时,base class 中的函数就被覆盖了,virtual 函数也是这样,但是有运行时多态,但是普通函数当想继承的时候就出问题了

在 derived class 加入 using base::func; 后相同参数和返回还是会覆盖 base class 中的对象

Item 34 - 区分接口继承和实现继承

C++ 的隐式规则太多,需要继承的东西干脆显示化,减少出错

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

non-virtual interface, NVI 手法也是将真正需要的改变的部分分离出来放进一个函数内,而虚函数内先处理前置条件,再调用该函数
```cpp
7 class Game {
8 public:
9 int health() const {
10 std::cout << "Game::health()\n";
11 int ret = doHealth();
12 std::cout << "Game:: ret " << ret << '\n';
13 return ret;
14 }
15 private:
16 virtual int doHealth() const {
17 std::cout << "Game:: doHealth()\n";
18 return 1;
19 }
20 };
21
22 class LOL : public Game {
23 private:
24 virtual int doHealth() const {
25 std::cout << "LOL doHealth()\n";
26 return 2;
27 }
28 };
29
30 int main() {
31 LOL lol;
32 lol.health();
33 }
// 可以使用到 base class 默认的析构函数,参考 Item39,用 private 作为一种实现方式。
// LOL 未定义虚函数的时候,结果为
// Game::health()
// Game:: doHealth()
// Game:: ret 1

// 定义虚函数后
// Game::health()
// LOL doHealth()
// Game:: ret 2
```

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpjxwp.html