Tip

在开发任何库前,先去调研一下别人是怎么做的,看看 STL/QT/Boost。

二、构造/析构/赋值运算

2.3 别让异常逃离析构函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class DBConn
{
public:
...
~DBConn()
{
if(!_closed)
{
try
{
db.close(); // 关闭连接
}
catch(...)
{
// 制作运转记录,记下对 close 的调用失败或吞下异常
...
}
}
}

void close()
{
_db.close(); // 供客户使用的新函数
_closed = true;
}

private:
DBConnection _db;
bool _closed;
};

以上可以保证即使用户操作错误也不会抛出异常(可以提醒用户手动解决),不抛出异常便不会影响程序的结束。

四、设计与声明

4.3 考虑写出一个不抛异常的 swap 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Called File
namespace WidgetStuff
{
...
template<typename T>
class Widget
{
public:
...
void swap(Widget& other)
{
using std::swap;
swap(pImpl, other.pImpl);
}
...
};
...
template<typename T>
void swap(Widget<T>& a, Widget<T>& b)
{
a.swap(b);
}
}

// Call File
template<typename T>
void doSomething(T& obj1, T& obj2)
{
...
using std::swap;
swap(obj1, obj2);
...
}

理一下 C++ 编译器的查找逻辑:

  1. 发现 Call File 中的 swap() 调用;
  2. 全局查找 swap()(没找到对应的);
  3. 查找 T 所在的 namespace 中的 swap()(找到);
  4. namespace WidgetStuff 中的 swap 函数调用 Widget 的 swap();
  5. 使用 std::swap()。

以上便能实现对 swap() 的偏特化。

5.5 区分接口继承和实现继承

  1. 实现继承代表你确定该方法一定需要被重载,子类的实现是完全不一样的;
  2. 接口继承意味着你确定子类一定会有这个方法,但是不确定会不会有特例,此时你应该提供一个普适的方法;
  3. 不继承则是表示所有子类都统一使用同一个实现。
  • 对于实现继承,应该将函数声明为 public 纯虚函数;
  • 对于接口继承,应该将函数声明为 public 纯虚函数,同时提供一个 protected 的普适方法供子类在重载虚函数时调用;
  • 对于不继承,应该将函数声明为 public non-virtual 函数。

6.1 Strategy design patterns

尝试通过 std::function 去完成它吧。

用代理类来解决一些问题

Lazy Expression Evaluation and Over-eager Evaluation

  • 对于调用不多的,可以用 Lazy Expression Evaluation,用到时再赋值/计算。
  • 相反对于调用多的,可以通过 Over-eager Evaluation 来提前计算,具体方法一般是牺牲空间储存一些实时计算的值,或者像 vector 扩容一样。

内存管理

慎重的选择容器

vector 并不能算作一个 STL 容器,开发中尽量少去使用,可以用 dequeue 代替。