在C++面向对象编程中,多态是实现灵活、可扩展代码的重要手段。它主要通过虚函数(virtual function)和纯虚函数(pure virtual function)来实现。下面从概念、机制、使用场景以及典型示例等方面进行详细解析。
1. 虚函数(Virtual Function)
1.1 定义
在基类中声明一个成员函数为virtual,意味着该函数可以在派生类中被覆盖(override)。调用时会根据对象的动态类型(实际对象的类型)决定执行哪个实现。
class Shape {
public:
virtual void draw() const { std::cout << "Shape::draw\n"; }
};
1.2 机制
- 虚表(vtable):编译器为每个含有虚函数的类生成一个指向函数实现的表。
- 虚指针(vptr):每个对象在运行时会携带一个指向其类虚表的指针。
- 调用虚函数时,编译器会先通过对象的vptr找到对应的虚表,然后再通过虚表中的函数指针调用具体实现。
1.3 特点
- 可覆盖:派生类可以重写基类的实现。
- 可实例化:基类可以被实例化,只要它至少有一个非纯虚函数。
- 默认实现:基类可以提供一个默认实现,派生类若不重写,仍会使用基类实现。
2. 纯虚函数(Pure Virtual Function)
2.1 定义
在基类中声明一个函数为纯虚,语法为= 0。这表示该函数没有实现,派生类必须覆盖它。
class Shape {
public:
virtual void draw() const = 0; // 纯虚函数
};
2.2 机制
- 纯虚函数同样在虚表中占位,但不指向任何实现。
- 对于含有至少一个纯虚函数的类,编译器会把该类标记为抽象类(abstract class)。抽象类不能被实例化。
2.3 特点
- 强制实现:任何非抽象派生类都必须覆盖纯虚函数,否则仍为抽象类。
- 无默认实现:纯虚函数本身没有实现,只能在派生类中提供。
- 用途:用于定义接口(Interface),只关心行为而不关心具体实现。
3. 何时使用虚函数,何时使用纯虚函数
| 场景 | 选择 |
|---|---|
| 需要提供默认实现,派生类可选择性覆盖 | 虚函数 |
| 必须强制派生类实现某个功能 | 纯虚函数 |
| 需要实现接口,基类不应实例化 | 纯虚函数 |
| 想让基类既能被实例化也能作为多态基类 | 虚函数或纯虚函数取决于需求 |
4. 典型示例:图形绘制系统
#include <iostream>
#include <vector>
#include <memory>
class Shape {
public:
virtual void draw() const = 0; // 纯虚:每个形状都必须实现绘制
virtual ~Shape() = default; // 虚析构,保证正确析构
};
class Circle : public Shape {
double radius_;
public:
Circle(double r) : radius_(r) {}
void draw() const override {
std::cout << "Drawing Circle with radius " << radius_ << '\n';
}
};
class Rectangle : public Shape {
double width_, height_;
public:
Rectangle(double w, double h) : width_(w), height_(h) {}
void draw() const override {
std::cout << "Drawing Rectangle " << width_ << "x" << height_ << '\n';
}
};
class ShapeManager {
std::vector<std::unique_ptr<Shape>> shapes_;
public:
void addShape(std::unique_ptr <Shape> s) { shapes_.push_back(std::move(s)); }
void renderAll() const {
for (const auto& s : shapes_)
s->draw(); // 动态绑定,调用对应派生类的实现
}
};
int main() {
ShapeManager mgr;
mgr.addShape(std::make_unique <Circle>(5.0));
mgr.addShape(std::make_unique <Rectangle>(4.0, 3.0));
mgr.renderAll(); // 输出两种形状的绘制信息
return 0;
}
关键点
Shape为抽象基类,使用纯虚函数draw()强制派生类实现绘制。Circle、Rectangle提供各自的实现。ShapeManager通过基类指针(智能指针)管理不同类型的形状,实现统一接口。- 通过多态,
renderAll()不需要知道具体形状类型,只调用draw()即可。
5. 常见误区
-
忘记声明基类析构为虚
如果基类析构不是虚的,在使用基类指针删除派生对象时,可能只调用基类析构,导致资源泄漏。 -
误以为所有虚函数都必须是纯虚
纯虚函数适用于抽象接口,虚函数则可提供默认实现或作为可选覆盖。 -
将纯虚函数误写为
=0但不实现
如果派生类没有实现纯虚函数,编译器会报错,派生类仍为抽象类,无法实例化。
6. 结论
虚函数与纯虚函数是C++实现多态的核心机制。通过合理设计,既可以在基类中提供默认行为,又能强制派生类实现必要功能,从而构建灵活、可维护的面向对象代码结构。在实际项目中,掌握它们的区别与使用场景,是提升代码质量和可扩展性的关键。