Item32 确定你的public继承关系建模出is-a的关系
public继承的含义:
- is-a:student是一种person,person的范围更大
- 在函数传参的过程,eta能同时接受person和student两种变量,反之不能
class Person {};
class Student : public Person {};
void eta(const Person& p) {}
void study(const Student& s) {}
int main() {
Person p;
Student s;
eta(p);
eta(s);
study(s);
study(p);
return 0;
}
/*
* 报错信息
main.cpp:11:11: error: invalid initialization of reference of type ‘const Student&’ from expression of type ‘Person’
11 | study(p);
| ^
In file included from main.cpp:3:
item_32.h:14:27: note: in passing argument 1 of ‘void study(const Student&)’
14 | void study(const Student& s) {}
*/
通过public继承建模,应该慎重思考父类和子类的关系,考虑下面两个例子
- 企鹅是一种鸟,但是不会飞,建模方式1不太恰当
- 如果方式2不需要区分会飞和不会飞,建模2显得多余
- 方式3将编译器能够限制的错误推迟到了运行期,同样不恰当
- 方式4不定义这样的方法,如果子类觉得有必要在定义,有更广的应用范围
// 建模方式1
class Bird {
public:
virtual void fly() {}
};
class Penguin : public Bird {};
// 建模方式2
class Bird {};
class FlyingBird : public Bird {
public:
virtual void fly() {}
};
class Penguin : public Bird {};
// 建模方式3
class Bird {
public:
virtual void fly() {}
};
class Penguin : public Bird {
public:
virtual void fly() { error(); }
};
// 建模方式4
class Bird {};
class Penguin : public Bird {};
- 如果是正方形继承自长方形,可以看出结果不符合预期
class Rectangle {
public:
virtual void setHeight(int newHeight);
virtual void setWidth(int newWidth);
virtual int height() const;
virtual int width() const;
};
class Square : public Rectangle {};
void makeBigger(Rectangle& r) {
int oldHeight = r.height();
r.setHeight(r.width() + 10);
assert(r.height() == oldHeight);
}
Square s;
makeBigger(s); // 结果不符合预期
Item34 区分接口继承和实现继承
public继承由两部分组成:接口继承和实现继承
- 声明纯虚函数:子类只继承函数接口
- 声明虚函数:子类继承函数接口,和一份缺省的代码实现
- 声明非虚函数:子类继承函数接口,并且不允许重写;
tips:82法则
- 程序有80%的时间花在20%的代码上
- 意味着平均函数调用可以有80%的虚函数而不影响效率
声明虚函数子类同时继承了接口和缺省的实现,可能存在危险,考虑以下场景:
- 航空公司买入了新的C型飞机,由于失误继承了缺省实现,出现不符合预期行为
class Airport {};
class Airplane {
public:
virtual void fly(const Airport& destination) {
// 飞机飞到指定目的地
}
};
// A和B飞行方式相同
class ModelA : public Airplane {};
class ModelB : public Airplane {};
// C型和A和B飞行方式不同
class ModelC : public Airplane {
public:
// 这里应该实现一种新飞行方式
};
- 需要切断同时继承接口和缺省实现的行为,将fly继承为纯虚函数,并提供默认的方法
- 纯虚函数意味着必须由自己的实现;
- 默认的实现意味着有相同行为的子类可以重复调用;
class Airport {};
class Airplane {
public:
virtual void fly(const Airport& destination) = 0;
};
void Airplane::fly(const Airport& destination) {
// 飞机飞到指定目的地
}
// A和B飞行方式相同
class ModelA : public Airplane {
public:
virtual void fly(const Airport& destination) {
Airplane::fly(destination);
}
};
class ModelB : public Airplane {
public:
virtual void fly(const Airport& destination) {
Airplane::fly(destination);
}
};
// C型和A和B飞行方式不同
class ModelC : public Airplane {
public:
virtual void fly(const Airport& destination) {
// 这里应该实现一种新飞行方式
}
};
Item36 绝不重新定义继承来的non-virtual函数
- 非虚函数的成员函数都是静态绑定
- 通过pB查找函数,只能找到B作用域内的函数;
- 通过pD查找函数,由于D内的同名函数覆盖了B的函数,所以只能找到D内的重名版本;
class B {
public:
void mf() {
std::cout << "B::mf()\n";
}
};
class D : public B {
public:
void mf() {
std::cout << "D::mf()\n";
}
};
int main() {
D x;
B* pB = &x;
pB->mf();
D* pD = &x;
pD->mf();
return 0;
}
// 输出
// B::mf()
// D::mf()