条款18:让接口容易被正确使用,不易被误用
好的接口应当被正确使用,不容易被误用。
促进正确使用的办法:接口的一致性、内置类型行为兼容。
阻止误用的办法:建立新类型(如在日期类中加入年月日类封装数据以避免非法数据)、限制类型上的操作(见条款03),束缚对象值,消除客户的资源管理责任(见条款13)。
条款19:设计
class
犹如设计type
设计一个高效的classes
要当做设计type
,遵循以下规范:
1.为新type
设计合理的构造、析构、内存分配与释放函数。
2.不要混淆构造函数与赋值操作符的行为(初始化与赋值)
3.copy
构造函数用来定义一个type
的pass-by-value
该如何实现。
4.成员函数要进行输入错误检查工作,避免非法数据。
5.考虑到class
是否继承自其他class
,以及是否会被其他class
继承。
6.设计所需要的类型转换函数。
7.设计合理的成员函数和操作符。
8.若不想使用编译器自动生成的函数,就该明确拒绝(见条款06)。
9.为成员函数确定属性:public、protected、private。
10.见条款29。
11.若新type
足够一般化,也许需要定义新的class template
。
12.若只是为已有的class
添加功能,也许单纯定义non-member
函数或templates
更合适。
条款20:宁以
pass-by-reference-to-const
替换pass-by-value
class Person {…};
class Student: public Person {…};
bool validateStudent(Student s); //函数以by value方式接受学生
Student plato;
bool platoIsOk = validateStudent(plato);
以上代码执行时,会调用Student
的copy
构造函数、Person
的copy
构造函数、以及构造他们各自的私有成员对象和析构函数,可见成本很高。
bool validateStudent(const Student& s);
用引用传值则高效得多,不用创建任何对象,不调用任何构造或析构函数。const
确保函数不会对传入的Student
做改变。引用传值也可以避免对象切割问题:即将派生类对象以传值方式传递给基类对象时,基类的构造函数被调用,会将除了基类对象所需成员之外的其他类成员切割掉,而引用传值则不会出现这种问题。
条款21:必须返回对象时,别妄想返回其
reference
尽管在条款20中讲到引用比传值开销更低,但是要避免在使用引用时,传递的是一个reference指向并不存在的对象。
class Rational
{
private:
int n,d;
friend
const Rational operator* (const Rational& r1, const Rational& r2){…}
public:
Rational(int n = 0, int d = 1);
};
Rational a(1,2);
Rational b(3,5);
Rational c = a * b;
在调用operator*时返回的是一个两个Rational对象的乘积的引用,但是期望这个含有乘积(a * b)的Rational对象本来就存在是不合理的,因此必须要创建这个Rational对象。
下面三种创建对象的方法都是不可取的:
1.创建在栈空间。
const Rational& operator* (const Rational& r1, const Rational& r2)
{
Rational result(r1.n * r2.n, r1.d * r2.d);
return result; //result是个local对象,函数退出前就销毁了
}
2.创建在堆上。
const Rational& operator* (const Rational& r1, const Rational& r2)
{
Rational* result = new Rational(r1.n * r2.n, r1.d * r2.d);
return *result; //无人来delete以释放result的内存,导致资源泄露
}
3.创建local static
对象。
const Rational& operator* (const Rational& r1, const Rational& r2)
{
static Rational result;
result = … ;
return result;
}
bool operator==(const Rational& r1, const Rational& r2);
Rational a,b,c,d;
…
if((a * b) == (c * d)) {
…
} else {
…
}
在上述代码中,(a * b) == (c * d)
总是为true
,原因是(a * b) == (c * d)
等价于 operator==(operator*(a, b), operator*(c, d))
,在operator==
被调用前,已经有两个operator*
调用式起作用了,每一个都返回reference
指向operator*
内部定义的static Rational
对象,虽然两次operator*
各自改变了static Rational
对象值,但是由于返回的是引用,所以调用端看到的永远是static Rational
对象的“现值”,所以两次是相同的。