跳转至

Inheritance & Polymorphism

继承与 is-a 关系

C++ 允许一个类从另一个类继承属性和方法。被继承的类称为基类(父类),而继承的类称为派生类(子类)。派生类可以访问基类的公共和保护成员,但不能访问私有成员。

1
2
3
struct Base { int x; };
struct Derive : Base { int y; };
struct Derive2 : public Derive { int z; };

继承同样可以应用访问控制的概念,语法如上。这里的 public 也可以是 protectedprivate,这表示公开继承、保护继承或私有继承。

  • 公开继承:基类的 publicprotected 成员在派生类中仍然是 publicprotected。基类的 private 成员在派生类中不可访问(除非基类将派生类声明为 friend)。
  • 保护继承:基类的 publicprotected 成员在派生类中成为 protected,而基类的 private 成员不可访问。
  • 私有继承:基类的 publicprotected 成员在派生类中成为 private,而基类的 private 成员不可访问。

另外,structclass 的区别在继承时同样适用。

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 对象,反之则不成立:

1
2
3
4
Derive2 d;
d.z; // z from Derive2
d.y; // y from Derive
d.x; // x from Base

我们称这种关系为 is-a 关系,例如 Derive2 is-a DeriveDerive is-a Base。这意味着 Derive2Derive 的一种子类型,而 DeriveBase 的一种子类型(这就是 Derive is-a Base 的含义)。这是具有传递性的,所以 Derive2 也是 Base 的一种子类型。由于继承,我们可以通过 Derive2 对象访问到 DeriveBase 的成员。

对象切片

因为 DeriveBase 的一种子类型,所以我们可以将 Derive 对象赋值给 Base 对象:

1
2
3
4
5
6
7
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 被丢弃掉了,这就是对象切片object slicing。对象切片也可能出现在函数调用中——因为参数是按值传递的!

1
2
3
4
void f(Base b);

Derive d {6, 42}; // x = 6, y = 42
f(d);             // object slicing

这就是“子对象”的一个体现

使用指针或引用可以避免这个问题,因为指针与引用并没有创建新的 Base 对象。

1
2
3
4
5
6
7
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
}

TBD

覆盖

派生类可以覆盖override基类的成员函数,即在派生类中重新定义一个与基类同名的函数。覆盖的函数必须具有相同的参数列表和返回类型。考虑下面的例子:

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;
};

多重继承

一个类可以继承多个基类:

1
2
3
4
struct Worker { void work(); };
struct Student { void study(); };

struct WorkingStudent : Worker, Student {};

菱形继承问题

1
2
3
4
5
6
7
8
struct A { int data; };
struct B : A {};
struct C : A {};
struct D : B, C {};

D d;
// d.data = 10;    // 错误:存在二义性
d.B::data = 10;     // 明确指定路径

虚基类

使用虚继承解决菱形继承问题:

1
2
3
4
5
6
7
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++ 默认行为)

1
2
3
4
5
6
7
struct Base {
  virtual void process(Base*);
};

struct Derive : Base {
  void process(Derive*) override; // 错误:参数类型不匹配
};

评论