explorer

万丈高楼平地起,勿在浮沙筑高台

0%

[What] Effective C++ :设计与声明

说明接口设计的准则。

让接口容易被正确使用,不易被误使用

当接口有多个参数时

当接口有多个参数的情况下,作为该接口的用户很容易将参数传递错误。

如果能在编译期就能够检查出该错误,则对用户是更加友好的方式。

假设一个日期类的构造函数设计如下:

1
2
3
4
class Date{
public:
Date(int month, int day, int year);
};

那么用户完全有可能将传递的顺序搞错:

1
Date date(30, 3, 1995);

可以通过新建类型来避免顺序出错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct Day{
// 这里使用 explicit 来限制隐式转换
explicit Day(int d): val(d){

}
int val;
};
struct Month{
explicit Month(int m): val(m){

}
int val;
};
struct Year{
explicit Year(int y): val(y){

}
int val;
};

class Date{
public:
Date(const Month& m, const Day& d, const Year& y);
};

通过以上修改,那么在调用时,可以在编译期检查出错误:

1
2
3
Date date(30, 3, 1995); // 错误,由于无法进行隐式转换,所以编译器会报错
Date date1(Day(30), Month(3), Year(1995)); // 错误,类型不匹配
Date date2(Month(3), Day(30), Year(1995)); // 正确,类型匹配

设计一致的接口

当类有相关性时,他们的接口应该尽量设计的一致,以让用户能够很快的接受。

比如容器类都有empty()这样的接口。

降低用户的负担

尽量不要出现需要用户来管理资源的情况,用户可能会忘记这么做。

比如动态申请的内存,应该返回智能指针。这样不需要用户来主动释放。

设计 class 犹如设计 type

设计一个 class/type 需要问以下问题:

  • 这个类对象应该如何被创建和销毁?

    这会影响类的构造、拷贝构造、移动构造、析构、内存管理重载等等……

  • 这个类对象的初始化和赋值是怎么样的?

    这会影响类的拷贝赋值、移动赋值

  • 这个类的成员变量合法值范围是什么?

    这回影响类的成员类型检查、范围检查

  • 这个类需要继承其他类或被继承吗?

    如果继承其他类,就要初始化基类,以及查看它是否有虚函数需要重载

    如果会被继承,就要考虑是否要设计虚函数,如有虚函数,那么析构也应该是虚析构

    还要考虑哪些是可以 protected

  • 这个类可以类型转换吗?

    如果需要转换为其他类型,那么需要设计转换函数

  • 这个类有操作符重载吗?

    为了类更加易于使用,需要设计操作符重载

  • 这个类接口是否易于使用?

pass by reference to const 优于 pass by value

调用函数时,如果使用pass by value的形式,那么实际上获得的是实参的拷贝。

对于一个类对象而言,其实是调用了其拷贝构造函数来创建一个临时对象。

函数的返回也是一样的道理。

如果一个对象的拷贝构造比较耗时,那么使用pass by value的形式,就会大大降低效率。

这个时候,使用pass by reference to const才是明智的决定。

返回对象时,慎重返回其 reference

如果返回的对象是一个 local 对象,那么在函数返回后,该对象的内存就被释放了。

那引用指向的内存及其后的行为就是未定义的!

大部分情况下都不应该返回 reference,如果确定要返回,那么就需要慎重考虑。