C++中的多态实现:虚函数与纯虚函数的区别

在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()强制派生类实现绘制。
  • CircleRectangle 提供各自的实现。
  • ShapeManager 通过基类指针(智能指针)管理不同类型的形状,实现统一接口。
  • 通过多态,renderAll()不需要知道具体形状类型,只调用draw()即可。

5. 常见误区

  1. 忘记声明基类析构为虚
    如果基类析构不是虚的,在使用基类指针删除派生对象时,可能只调用基类析构,导致资源泄漏。

  2. 误以为所有虚函数都必须是纯虚
    纯虚函数适用于抽象接口,虚函数则可提供默认实现或作为可选覆盖。

  3. 将纯虚函数误写为=0但不实现
    如果派生类没有实现纯虚函数,编译器会报错,派生类仍为抽象类,无法实例化。

6. 结论

虚函数与纯虚函数是C++实现多态的核心机制。通过合理设计,既可以在基类中提供默认行为,又能强制派生类实现必要功能,从而构建灵活、可维护的面向对象代码结构。在实际项目中,掌握它们的区别与使用场景,是提升代码质量和可扩展性的关键。

发表评论