第16章 类的多态

多态的概念

多态是面向对象编程的三大核心特性之一,它允许不同类型的对象对同一消息做出不同的响应。多态的本质是”一个接口,多种实现”。

多态的实现方式

  1. 编译时多态:通过函数重载和运算符重载实现
  2. 运行时多态:通过虚函数和继承实现

虚函数

虚函数是实现运行时多态的基础,它允许在派生类中重新定义基类的方法。

虚函数的声明

1
2
3
4
5
6
class Base {
public:
virtual void show() {
std::cout << "Base class show()" << std::endl;
}
};

虚函数的重写

1
2
3
4
5
6
class Derived : public Base {
public:
void show() override {
std::cout << "Derived class show()" << std::endl;
}
};

虚析构函数

当使用基类指针指向派生类对象时,为了确保正确调用派生类的析构函数,基类的析构函数应该声明为虚函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Base {
public:
virtual ~Base() {
std::cout << "Base destructor" << std::endl;
}
};

class Derived : public Base {
public:
~Derived() {
std::cout << "Derived destructor" << std::endl;
}
};

抽象类和纯虚函数

抽象类是不能实例化的类,它通常包含一个或多个纯虚函数。

纯虚函数的声明

1
2
3
4
class AbstractBase {
public:
virtual void pureVirtual() = 0; // 纯虚函数
};

抽象类的继承

派生类必须实现基类中的所有纯虚函数,否则派生类也会成为抽象类。

1
2
3
4
5
6
class ConcreteDerived : public AbstractBase {
public:
void pureVirtual() override {
std::cout << "Implemented pure virtual function" << std::endl;
}
};

接口类

在C++中,接口类是只包含纯虚函数的抽象类,它定义了一组接口规范。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Shape {
public:
virtual double area() const = 0;
virtual double perimeter() const = 0;
virtual ~Shape() = default;
};

class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double area() const override {
return M_PI * radius * radius;
}
double perimeter() const override {
return 2 * M_PI * radius;
}
};

class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() const override {
return width * height;
}
double perimeter() const override {
return 2 * (width + height);
}
};

多态的应用

多态的使用场景

  1. 统一接口:通过基类指针或引用调用派生类的方法
  2. 扩展系统:添加新的派生类而不需要修改现有代码
  3. 代码复用:通过继承和多态实现代码的复用

多态的实现原理

C++通过虚函数表(vtable)和虚指针(vptr)实现运行时多态:

  1. 每个包含虚函数的类都有一个虚函数表
  2. 每个类的对象都包含一个指向虚函数表的指针(虚指针)
  3. 当调用虚函数时,通过虚指针找到虚函数表,再根据函数在表中的位置调用相应的函数

多态与类型转换

向上转换

向上转换是将派生类指针或引用转换为基类指针或引用,这是安全的隐式转换。

1
2
Derived derived;
Base* basePtr = &derived; // 向上转换,安全

向下转换

向下转换是将基类指针或引用转换为派生类指针或引用,需要使用dynamic_cast进行安全转换。

1
2
3
4
5
6
Base* basePtr = new Derived();
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
if (derivedPtr) {
// 转换成功
derivedPtr->derivedMethod();
}

多态的局限性

  1. 性能开销:虚函数调用比普通函数调用慢
  2. 内存开销:每个对象需要额外的虚指针
  3. 复杂性:增加了代码的复杂性和理解难度

多态的最佳实践

  1. 合理使用虚函数:只在需要多态的地方使用虚函数
  2. 使用override关键字:明确表示重写基类的虚函数
  3. 使用final关键字:防止虚函数被进一步重写
  4. 合理设计类层次:避免过深的继承层次

现代C++中的多态特性

final关键字的使用

final关键字可以用于类或虚函数,防止类被继承或虚函数被重写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 防止类被继承
class FinalClass final {
public:
virtual void method() {
std::cout << "FinalClass::method()" << std::endl;
}
};

// 错误:不能继承final类
// class DerivedFromFinal : public FinalClass { };

// 防止虚函数被重写
class Base {
public:
virtual void method() final {
std::cout << "Base::method()" << std::endl;
}
};

class Derived : public Base {
public:
// 错误:不能重写final虚函数
// void method() override { }
};

虚函数的默认参数

虚函数的默认参数是静态绑定的,即使用的是基类中定义的默认参数值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Base {
public:
virtual void method(int value = 10) {
std::cout << "Base::method(" << value << ")" << std::endl;
}
};

class Derived : public Base {
public:
void method(int value = 20) override {
std::cout << "Derived::method(" << value << ")" << std::endl;
}
};

int main() {
Base* basePtr = new Derived();
basePtr->method(); // 输出: Base::method(10),使用基类的默认参数
delete basePtr;
return 0;
}

纯虚函数的默认实现(C++11+)

C++11允许为纯虚函数提供默认实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Base {
public:
virtual void pureVirtual() = 0;
};

// 纯虚函数的默认实现
void Base::pureVirtual() {
std::cout << "Base::pureVirtual() default implementation" << std::endl;
}

class Derived : public Base {
public:
void pureVirtual() override {
// 可以调用基类的默认实现
Base::pureVirtual();
std::cout << "Derived::pureVirtual()" << std::endl;
}
};

多态与智能指针的结合

使用智能指针管理多态对象,确保内存安全:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <memory>

class Shape {
public:
virtual void draw() const = 0;
virtual ~Shape() = default;
};

class Circle : public Shape {
public:
void draw() const override {
std::cout << "Drawing a circle" << std::endl;
}
};

class Rectangle : public Shape {
public:
void draw() const override {
std::cout << "Drawing a rectangle" << std::endl;
}
};

int main() {
// 使用unique_ptr管理多态对象
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>());
shapes.push_back(std::make_unique<Rectangle>());

// 多态调用
for (const auto& shape : shapes) {
shape->draw();
}

// 智能指针自动释放内存
return 0;
}

示例:图形系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <iostream>
#include <memory>
#include <vector>

class Shape {
public:
virtual void draw() const = 0;
virtual ~Shape() = default;
};

class Circle : public Shape {
private:
int x, y, radius;
public:
Circle(int x, int y, int radius) : x(x), y(y), radius(radius) {}
void draw() const override {
std::cout << "Drawing Circle at (" << x << ", " << y
<< ") with radius " << radius << std::endl;
}
};

class Rectangle : public Shape {
private:
int x, y, width, height;
public:
Rectangle(int x, int y, int width, int height)
: x(x), y(y), width(width), height(height) {}
void draw() const override {
std::cout << "Drawing Rectangle at (" << x << ", " << y
<< ") with width " << width << " and height " << height << std::endl;
}
};

int main() {
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(10, 10, 5));
shapes.push_back(std::make_unique<Rectangle>(20, 20, 10, 8));

// 多态调用draw()方法
for (const auto& shape : shapes) {
shape->draw();
}

return 0;
}

总结

多态是C++面向对象编程的重要特性,它通过虚函数机制实现了运行时的动态绑定,使得代码更加灵活和可扩展。合理使用多态可以提高代码的可读性、可维护性和可扩展性,是现代C++编程中的重要技术之一。