条款21
在栈上创建对象
const Rational& operartor* (const Rational& lhs, const Rational& rhs) {
Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
return result;//错误代码!
}
result是个local对象,在函数退出前就被销毁了
在堆上创建对象
const Rational& operartor* (const Rational& lhs, const Rational& rhs) {
Rational * result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);//更糟糕的代码
return result;
}
Rational w, x, y, z;
w = x * y * z;
这一句调用了两次operator*,因为使用了两次new,也就需要两次delete。但却没有合理的方法让operator* 使用者进行那些delete调用,因为没有合理的办法让他们取得operator*返回的引用背后隐藏的那个指针。将会导致资源泄漏。
让operator*返回的引用指向一个被定义在函数内部的static对象
const Rational& operartor* (const Rational& lhs, const Rational& rhs) {
static Rational result;
result = ...;
return result;
}
//当执行下面的语句
bool operator==(const Rational& lhs, const Rational& rhs);
Rational a, b, c, d;
if ((a * b) == (c * d)) {
//... //总是会执行这一局
}
else {
//...
}
两次operator* 调用的确各自改变了static Rational对象值,但是由于他们返回的都是reference, 因此调用端看到的永远是static Rational对象的“现值”。
总结:
- 别返回对象的引用
条款22
将成员变量声明为private,就是隐藏成员变量,也就是封装他们,只有成员函数可以改变他们
public意味着不封装,不封装意味着不可改变,特别是对被广泛使用的classes而言。
protected成员变量就像public成员变量一样缺乏封装性。
条款23
宁以non-member、non-friend替换member函数
条款27
class Window {
public:
virtual void onResize() {...}
};
class SpecialWinddow : public Window {
public:
virtual void onResize() {
static_cast<Window>(*this).onResize();//将*this转换成Window,然后调用其onResize;这样不行!
}
};
这里会发生什么问题呢?static_cast(*this)这个转型动作并不是如你想象的那样得到当前对象的基类对象部分,其实编译器为你产生了的是当前对象的基类部分的副本
条款28,29
避免返回handles(包括reference、指针、迭代器)指向对象内部。遵守这个条款可增加封装性,帮助const成员函数的行为像个const,并将发生“虚吊号码牌”(dangling handles)的可能性降至最低。
条款30,31
inline只是对编译器的一个申请,不是强制命令
条款30,31参考
条款32
public inheritance(公有继承)意味“is-a”的关系。在进行面向对象类设计时,需要明确继承之前是否是"is-a"关系,以决定derived类与base类设计的合理性。
书中有2个例子:鸟和企鹅关于fly()函数的讨论,以及矩形和正方形关于长宽的讨论
结论:适用于base classes身上的每一件事一定也适用于derived身上,因为每一个derived对象也是一个base class 对象。(企鹅不会飞,而鸟类一般会飞,矩形长宽可以不一样正方形必须一样,所以必须采取措施解决公有继承带来的问题)
条款34
- pure virtual函数的目的是为了让derived classes只继承函数接口
- impure virtual函数会提供一份实现代码,derived class可能覆盖它
- non-virtual函数具体指定接口继承以及强制性实现继承