多态是面向对象编程三大特性之一(封装、继承、多态),它让代码具备了"以不变应万变"的能力。想象你是一个音乐会的检票员,面对普通观众、VIP会员和残障人士,虽然都是"检票"这个动作,但具体执行方式各不相同——这就是多态在现实世界的完美映射。
在C++中,多态特指通过虚函数机制实现的动态绑定(dynamic binding)。当基类指针或引用指向派生类对象时,调用虚函数会根据实际对象类型决定执行哪个版本的函数。这种机制包含三个关键特征:
关键理解:多态不是简单的"if-else"分支,而是通过虚函数表(vtable)实现的动态分派机制。每个包含虚函数的类都会有一个虚函数表,存储该类所有虚函数的地址。
静态多态主要通过模板和函数重载实现:
cpp复制// 函数重载示例
void print(int i) { cout << "Integer: " << i << endl; }
void print(double f) { cout << "Double: " << f << endl; }
// 模板示例
template<typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
静态多态的特点:
动态多态的核心是虚函数和继承体系:
cpp复制class Shape {
public:
virtual void draw() = 0; // 纯虚函数
virtual ~Shape() {} // 虚析构函数
};
class Circle : public Shape {
public:
void draw() override { cout << "Drawing a circle" << endl; }
};
class Square : public Shape {
public:
void draw() override { cout << "Drawing a square" << endl; }
};
动态多态的特点:
每个包含虚函数的类都有一个虚函数表,这是一个函数指针数组,存储该类所有虚函数的地址。当对象被创建时,编译器会在对象内存布局的最前面添加一个vptr指针指向这个表。
内存布局示例:
code复制+------------------+
| vptr | → 指向虚函数表
| 成员变量 |
| ... |
+------------------+
虚函数调用过程:
cpp复制// 好的继承设计示例
class IAnimal {
public:
virtual void speak() = 0;
virtual ~IAnimal() {}
};
class Dog : public IAnimal {
public:
void speak() override { cout << "Woof!" << endl; }
};
cpp复制class Base {
public:
virtual Base* clone() const { return new Base(*this); }
};
class Derived : public Base {
public:
Derived* clone() const override { // 协变返回类型
return new Derived(*this);
}
};
当派生类对象被直接赋值给基类对象(而非指针/引用)时,会发生对象切片(Object Slicing),丢失派生类特有的成员:
cpp复制class Base { /*...*/ };
class Derived : public Base { int extra_data; };
Derived d;
Base b = d; // 发生切片,extra_data丢失
解决方案:
现代C++推荐使用智能指针管理多态对象:
cpp复制std::unique_ptr<Shape> createShape(ShapeType type) {
switch(type) {
case CIRCLE: return std::make_unique<Circle>();
case SQUARE: return std::make_unique<Square>();
default: throw std::invalid_argument("Unknown shape type");
}
}
auto shape = createShape(CIRCLE);
shape->draw(); // 正确调用Circle的draw方法
虚函数调用相比普通函数调用多出以下开销:
实测数据(i7-9700K, GCC 10.2):
| 调用类型 | 调用次数/秒 |
|---|---|
| 直接调用 | 3.2亿 |
| 虚函数调用 | 2.8亿 |
cpp复制// CRTP示例
template <typename Derived>
class Base {
public:
void interface() {
static_cast<Derived*>(this)->implementation();
}
};
class Derived : public Base<Derived> {
public:
void implementation() {
cout << "Derived implementation" << endl;
}
};
通过多态实现运行时算法替换:
cpp复制class SortStrategy {
public:
virtual void sort(vector<int>& data) = 0;
virtual ~SortStrategy() {}
};
class QuickSort : public SortStrategy {
public:
void sort(vector<int>& data) override { /* 快速排序实现 */ }
};
class MergeSort : public SortStrategy {
public:
void sort(vector<int>& data) override { /* 归并排序实现 */ }
};
class Sorter {
unique_ptr<SortStrategy> strategy;
public:
void setStrategy(unique_ptr<SortStrategy> s) { strategy = move(s); }
void execute(vector<int>& data) { strategy->sort(data); }
};
利用多态实现灵活的对象创建:
cpp复制class Product {
public:
virtual void operation() = 0;
virtual ~Product() {}
};
class ConcreteProductA : public Product { /*...*/ };
class ConcreteProductB : public Product { /*...*/ };
class Creator {
public:
virtual unique_ptr<Product> create() = 0;
virtual ~Creator() {}
};
template<typename T>
class ConcreteCreator : public Creator {
public:
unique_ptr<Product> create() override {
return make_unique<T>();
}
};
当可能通过基类指针删除派生类对象时,基类必须有虚析构函数:
cpp复制class Base {
public:
virtual ~Base() {} // 必须为虚
};
class Derived : public Base {
int* resource;
public:
Derived() : resource(new int[100]) {}
~Derived() { delete[] resource; } // 需要被调用
};
Base* p = new Derived();
delete p; // 如果Base析构非虚,会导致内存泄漏
派生类定义与基类同名但签名不同的函数会导致隐藏:
cpp复制class Base {
public:
virtual void foo(int) {}
};
class Derived : public Base {
public:
void foo(double) {} // 隐藏了Base::foo(int)
};
Derived d;
d.foo(1); // 调用Derived::foo(double),可能非预期
解决方案:
cpp复制class Derived : public Base {
public:
using Base::foo; // 引入基类版本
void foo(double) {}
};
C++20允许虚函数在常量表达式中使用:
cpp复制struct Shape {
virtual constexpr int area() const = 0;
};
struct Square : Shape {
constexpr int area() const override { return side * side; }
int side;
};
constexpr Square s{5};
static_assert(s.area() == 25);
C++20协程可以与多态对象协同工作:
cpp复制struct Task {
struct promise_type {
Task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};
struct AsyncOperation {
virtual Task execute() = 0;
virtual ~AsyncOperation() {}
};
在实际工程中,多态的正确使用需要权衡灵活性和性能。我个人的经验法则是:在接口设计时优先考虑多态带来的扩展性,在性能关键路径上则要谨慎评估虚函数调用的开销。当发现某个虚函数被频繁调用成为瓶颈时,可以考虑用模板方法等编译期多态技术进行优化。