Inheritance & Polymorphism
继承与 is-a 关系
C++ 允许一个类从另一个类继承属性和方法。被继承的类称为基类(父类),而继承的类称为派生类(子类)。派生类可以访问基类的公共和保护成员,但不能访问私有成员。
| struct Base { int x; };
struct Derive : Base { int y; };
struct Derive2 : public Derive { int z; };
|
继承同样可以应用访问控制的概念,语法如上。这里的 public 也可以是 protected 或 private,这表示公开继承、保护继承或私有继承。
- 公开继承:基类的
public 和 protected 成员在派生类中仍然是 public 和 protected。基类的 private 成员在派生类中不可访问(除非基类将派生类声明为 friend)。
- 保护继承:基类的
public 和 protected 成员在派生类中成为 protected,而基类的 private 成员不可访问。
- 私有继承:基类的
public 和 protected 成员在派生类中成为 private,而基类的 private 成员不可访问。
另外,struct 与 class 的区别在继承时同样适用。
| DeriveA 与 DeriveB 均公开继承于 Base |
|---|
| struct DeriveA : /* public */ Base { /* ... */ };
class DeriveB : public Base { /* ... */ };
|
| DeriveA 与 DeriveB 均私有继承于 Base |
|---|
| struct DeriveA : private Base { /* ... */ };
class DeriveB : /* private */ Base { /* ... */ };
|
概念上,派生类的对象包含一个基类的子对象。语义上,派生类表示对基类的扩展。如果 Derive 继承自 Base,那么所有 Derive 对象都是 Base 对象,并且可以在任何期待 Base 对象的地方使用 Derive 对象,反之则不成立:
| Derive2 d;
d.z; // z from Derive2
d.y; // y from Derive
d.x; // x from Base
|
我们称这种关系为 is-a 关系,例如 Derive2 is-a Derive,Derive is-a Base。这意味着 Derive2 是 Derive 的一种子类型,而 Derive 是 Base 的一种子类型(这就是 Derive is-a Base 的含义)。这是具有传递性的,所以 Derive2 也是 Base 的一种子类型。由于继承,我们可以通过 Derive2 对象访问到 Derive 和 Base 的成员。
对象切片
因为 Derive 是 Base 的一种子类型,所以我们可以将 Derive 对象赋值给 Base 对象:
| struct Base { int x; };
struct Derive : Base { int y; };
int main() {
Derive d {6, 42}; // x = 6, y = 42
Base b {d}; // object slicing
}
|
但是问题在于,Base 并不知道 Derive 的存在,也不知道 Derive 还有一个 y 成员。所以当把 Derive 对象赋值给 Base 对象时,Base 只会保留它所拥有的成员 x,而 y 被丢弃掉了,这就是对象切片)。对象切片也可能出现在函数调用中——因为参数是按值传递的!
| void f(Base b);
Derive d {6, 42}; // x = 6, y = 42
f(d); // object slicing
|
这就是“子对象”的一个体现
使用指针或引用可以避免这个问题,因为指针与引用并没有创建新的 Base 对象。
| void g(Base &b);
int main() {
Derive d {6, 42}; // x = 6, y = 42
Base &b {d}; // no object slicing
g(d); // no object slicing
}
|
覆盖
派生类可以覆盖基类的成员函数,即在派生类中重新定义一个与基类同名的函数。覆盖的函数必须具有相同的参数列表和返回类型。考虑下面的例子:
| struct Base {
void f() { std::cout << "Base::f()\n"; }
};
struct Derive : Base {
void f() { std::cout << "Derive::f()\n"; }
};
int main() {
Derive d;
d.f(); // Derive::f()
d.Base::f(); // Base::f()
}
|
这里就发生了覆盖。Derive::f() 覆盖了 Base::f(),所以 d.f() 调用的是 Derived::f()
但这并不代表 Base::f() 不可用,仍然可以通过 d.Base::f() 调用。
静态绑定
这会有什么问题呢?
虚函数
动态绑定
使用 virtual 关键字声明虚函数,实现运行时多态(动态绑定):
| struct Base {
virtual void func() { cout << "Base func" << endl; }
};
struct Derive : Base {
void func() override { cout << "Derive func" << endl; }
};
int main() {
Derive d;
Base* pb = &d;
pb->func(); // 输出 Derive func(动态绑定)
}
|
Overload, Override & Name Hiding All In One
| struct A {
virtual void f() { std::cout << "Af" << std::endl; }
virtual void f(int) { std::cout << "Afi" << std::endl; }
virtual void f(int, int) { std::cout << "Afii" << std::endl; }
};
struct B : A {
void f() override { std::cout << "Bf" << std::endl; }
void f(int) override { std::cout << "Bfi" << std::endl; }
};
struct C : B {
void f() override { std::cout << "Cf" << std::endl; }
};
int main() {
C obj;
C &c = obj;
B &b = obj;
A &a = obj;
// for each one of the following calls, determine the output
// or whether it causes a compile error
a.f(); // output or compile error?
a.f(1); // output or compile error?
a.f(1, 2); // output or compile error?
b.f(); // output or compile error?
b.f(1); // output or compile error?
b.f(1, 2); // output or compile error?
c.f(); // output or compile error?
c.f(1); // output or compile error?
c.f(1, 2); // output or compile error?
}
|
答案
| a.f(); // "Cf"
a.f(1); // "Bfi"
a.f(1, 2); // "Afii"
b.f(); // "Cf"
b.f(1); // "Bfi"
b.f(1, 2); // compile error
c.f(); // "Cf"
c.f(1); // compile error
c.f(1, 2); // compile error
|
虚析构函数
当通过基类指针删除派生类对象时,必须将基类析构函数声明为虚函数:
| struct Base {
virtual ~Base() { cout << "Base dtor" << endl; }
};
struct Derive : Base {
~Derive() override { cout << "Derive dtor" << endl; }
};
int main() {
Base* pb = new Derive();
delete pb; // 正确调用派生类析构函数
}
|
纯虚函数与抽象类
包含纯虚函数的类成为抽象类,不能实例化:
| struct Shape {
virtual double area() const = 0; // 纯虚函数
};
struct Circle : Shape {
Circle(double r) : radius(r) {}
double area() const override { return 3.14 * radius * radius; }
private:
double radius;
};
|
多重继承
一个类可以继承多个基类:
| struct Worker { void work(); };
struct Student { void study(); };
struct WorkingStudent : Worker, Student {};
|
菱形继承问题
| struct A { int data; };
struct B : A {};
struct C : A {};
struct D : B, C {};
D d;
// d.data = 10; // 错误:存在二义性
d.B::data = 10; // 明确指定路径
|
虚基类
使用虚继承解决菱形继承问题:
| struct A { int data; };
struct B : virtual A {};
struct C : virtual A {};
struct D : B, C {};
D d;
d.data = 10; // 正确,只有一份 data
|
附录:协变、逆变与不变
协变(Covariant)
允许派生类覆盖方法的返回类型为基类返回类型的派生类:
| struct Base {};
struct Derive : Base {};
struct Factory {
virtual Base* create();
};
struct DeriveFactory : Factory {
Derive* create() override; // 协变返回类型
};
|
逆变(Contravariant)
参数类型与返回类型相反的变化(C++ 不支持)
不变(Invariant)
参数类型必须完全匹配(C++ 默认行为)
| struct Base {
virtual void process(Base*);
};
struct Derive : Base {
void process(Derive*) override; // 错误:参数类型不匹配
};
|