多态与虚函数

翻译整理自 cppreference - 虚函数。

1. 静态多态 vs 动态多态

  • 静态:函数重载、模板、CRTP——编译期决定
  • 动态:虚函数——运行期决定

2. virtual 虚函数

class Shape {
public:
    virtual double area() const { return 0; }
    virtual ~Shape() = default;                // 多态基类必虚析构
};

class Circle : public Shape {
public:
    Circle(double r) : r_(r) {}
    double area() const override {              // override 关键字
        return 3.14 * r_ * r_;
    }
private:
    double r_;
};

class Square : public Shape {
public:
    Square(double s) : s_(s) {}
    double area() const override { return s_ * s_; }
private:
    double s_;
};

void print(Shape* s) {
    std::cout << s->area();                     // 动态派发
}

print(new Circle(1));
print(new Square(2));

3. override 与 final

override 让编译器检查是否真的覆盖(C++11):

class D : public Base {
public:
    void f() override;     // 必须真的覆盖,否则报错
};

final 禁止再覆盖或继承:

class Base final {};                        // 不能再继承

class D : public Base {
public:
    void f() final;                         // 派生类不能再覆盖
};

4. 纯虚函数与抽象类

class Shape {
public:
    virtual double area() const = 0;        // 纯虚函数
    virtual ~Shape() = default;
};

// Shape s;                                  // ❌ 抽象类不可实例化
class Circle : public Shape {
public:
    double area() const override { return 1.0; }
};
Circle c;                                   // OK

5. 虚析构函数

多态基类必须虚析构,否则 delete base_ptr 不会调用派生析构:

class Base {
public:
    virtual ~Base() = default;
};

6. RTTI:dynamic_cast 与 typeid

Shape* s = new Circle(1);

if (auto c = dynamic_cast<Circle*>(s)) {
    // 安全向下转换成功
}

#include <typeinfo>
std::cout << typeid(*s).name();             // 类型名

RTTI 需虚函数支持,且开销较大。推荐用 static_cast 配合自定义类型标签。

7. 虚函数表 vtable

每个含虚函数的类都有一个 vtable,对象内部存有指向 vtable 的指针。调用虚函数时通过 vtable 间接查找目标函数。

class A {
public:
    virtual void f();
};
// sizeof(A) 通常 = sizeof(void*) = 8

8. 隐藏(不叫覆盖)

非虚同名函数在派生类会隐藏基类版本:

class Base { public: void f(int); };
class D : public Base {
public:
    void f(double);                          // 隐藏 Base::f(int)
};

D d;
d.f(1);                                      // 调用 D::f(double)
d.Base::f(1);                                // 显式调用基类

用 using Base::f; 引入:

class D : public Base {
public:
    using Base::f;                            // 把基类 f 引入派生
    void f(double);
};

9. 协变返回类型

虚函数覆盖可返回更具体的类型:

class Base {
public:
    virtual Base* clone() const;
};
class D : public Base {
public:
    D* clone() const override;               // 返回 D*,更具体
};

10. 性能权衡

  • 虚函数调用比普通函数多一次间接寻址
  • 难以被编译器内联
  • 在性能关键路径上慎用虚函数

小结

虚函数是 C++ 实现 OOP 多态的核心,配合 override/final 可让代码更安全。