重温C++编程-概念篇-多态_c++多态原理
多态,这是面向对象编程的核心概念,也是程序员最容易误解的概念。很多人以为多态就是虚函数,这是根本性的误解。
多态不是技术实现,而是设计思想。它体现了"一个接口,多种实现"的设计哲学。
多态体现了"抽象与具体"的辩证关系。它要求我们思考:什么是本质特征?什么是可变特征?
真正的多态应该让代码更灵活、更易扩展,而不是更复杂。好的多态让接口稳定,实现可变。
多态就像遥控器
多态提供了统一的接口,但允许不同的实现。这减少了认知负荷,提高了代码的可维护性。
多态应该基于行为的一致性,而不是数据的一致性。不同的对象可以有不同的内部状态,但应该有相同的行为接口。
多态让使用者不需要关心具体的实现细节,只需要知道如何使用接口。
class Device {
public:
virtual void powerOn() = 0;
virtual void powerOff() = 0;
virtual ~Device() = default;
};
class TV : public Device {
public:
void powerOn() override {
std::cout << "TV is turning on..." << std::endl;
}
void powerOff() override {
std::cout << "TV is turning off..." << std::endl;
}
};
class AirConditioner : public Device {
public:
void powerOn() override {
std::cout << "Air conditioner is starting..." << std::endl;
}
void powerOff() override {
std::cout << "Air conditioner is stopping..." << std::endl;
}
};
class Stereo : public Device {
public:
void powerOn() override {
std::cout << "Stereo is playing music..." << std::endl;
}
void powerOff() override {
std::cout << "Stereo is stopping..." << std::endl;
}
};
// 万能遥控器
void controlDevice(Device* device) {
device->powerOn(); // 同一个按钮
device->powerOff(); // 同一个按钮
}
int main() {
TV tv;
AirConditioner ac;
Stereo stereo;
controlDevice(&tv); // 控制电视
controlDevice(&ac); // 控制空调
controlDevice(&stereo); // 控制音响
}
多态就像变形金刚
多态允许同一个对象在不同的上下文中表现出不同的行为。这体现了"形式与内容"的辩证关系。
多态是许多设计模式的基础,如策略模式、状态模式等。
多态提供了良好的扩展性。添加新的实现不需要修改现有代码。
class Transformer {
public:
virtual void transform() = 0;
virtual void attack() = 0;
virtual ~Transformer() = default;
};
class CarTransformer : public Transformer {
public:
void transform() override {
std::cout << "Transforming into a car..." << std::endl;
}
void attack() override {
std::cout << "Car is attacking with wheels!" << std::endl;
}
};
class PlaneTransformer : public Transformer {
public:
void transform() override {
std::cout << "Transforming into a plane..." << std::endl;
}
void attack() override {
std::cout << "Plane is attacking with missiles!" << std::endl;
}
};
class RobotTransformer : public Transformer {
public:
void transform() override {
std::cout << "Transforming into a robot..." << std::endl;
}
void attack() override {
std::cout << "Robot is attacking with laser!" << std::endl;
}
};
// 战斗系统
void battle(Transformer* transformer) {
transformer->transform();
transformer->attack();
}
多态就像魔法师
魔法师可以施展不同的魔法,但都是通过同一个咒语。
这个比喻虽然不是很准确,但至少能让人理解个大概。
class Spell {
public:
virtual void cast() = 0;
virtual ~Spell() = default;
};
class Fireball : public Spell {
public:
void cast() override {
std::cout << "Fireball! " << std::endl;
}
};
class IceShard : public Spell {
public:
void cast() override {
std::cout << "Ice Shard! " << std::endl;
}
};
class Lightning : public Spell {
public:
void cast() override {
std::cout << "Lightning! " << std::endl;
}
};
class Wizard {
public:
void castSpell(Spell* spell) {
std::cout << "Wizard is casting..." << std::endl;
spell->cast(); // 同一个咒语,不同的魔法
}
};
虚函数表:多态的幕后英雄
虚函数表是C++实现多态的机制。它提供了运行时类型识别和动态绑定的能力。
每个有虚函数的类都有一个虚函数表,存储虚函数的地址。对象通过虚函数表指针访问虚函数。
虚函数调用有额外的间接寻址开销,但提供了灵活的多态性。
class Animal {
public:
virtual void eat() { std::cout << "Animal eating" << std::endl; }
virtual void sleep() { std::cout << "Animal sleeping" << std::endl; }
};
class Dog : public Animal {
public:
void eat() override { std::cout << "Dog eating" << std::endl; }
void sleep() override { std::cout << "Dog sleeping" << std::endl; }
};
class Cat : public Animal {
public:
void eat() override { std::cout << "Cat eating" << std::endl; }
void sleep() override { std::cout << "Cat sleeping" << std::endl; }
};
// 每个有虚函数的类都有一个虚函数表
// Animal的虚函数表:[Animal::eat, Animal::sleep]
// Dog的虚函数表:[Dog::eat, Dog::sleep]
// Cat的虚函数表:[Cat::eat, Cat::sleep]
void feedAnimal(Animal* animal) {
animal->eat(); // 通过虚函数表找到正确的函数
}
运行时类型识别:多态的侦探
运行时类型识别(RTTI)提供了在运行时确定对象类型的能力。它是多态的重要补充。
RTTI主要用于类型安全的向下转换和异常处理。
RTTI有运行时开销,应该谨慎使用。
class Animal {
public:
virtual ~Animal() = default;
};
class Dog : public Animal {
public:
void bark() { std::cout << "Woof!" << std::endl; }
};
class Cat : public Animal {
public:
void meow() { std::cout << "Meow!" << std::endl; }
};
void identifyAnimal(Animal* animal) {
if (typeid(*animal) == typeid(Dog)) {
std::cout << "This is a dog!" << std::endl;
Dog* dog = dynamic_cast<Dog*>(animal);
if (dog) {
dog->bark();
}
} else if (typeid(*animal) == typeid(Cat)) {
std::cout << "This is a cat!" << std::endl;
Cat* cat = dynamic_cast<Cat*>(animal);
if (cat) {
cat->meow();
}
}
}
抽象类:多态的蓝图
抽象类定义了接口契约,但不提供具体实现。它强制子类实现特定的行为。
抽象类应该定义稳定的接口,具体实现可以变化。
C++20的concepts提供了更好的接口定义方式。
class Shape {
public:
virtual void draw() = 0; // 纯虚函数,必须重写
virtual double area() = 0; // 纯虚函数,必须重写
virtual ~Shape() = default;
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
void draw() override {
std::cout << "Drawing a circle" << std::endl;
}
double area() override {
return 3.14159 * radius * radius;
}
};
class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
void draw() override {
std::cout << "Drawing a rectangle" << std::endl;
}
double area() override {
return width * height;
}
};
// 不能创建抽象类的实例
// Shape shape; // 编译错误
// 但可以创建派生类的实例
Circle circle(5.0);
Rectangle rectangle(4.0, 6.0);
虚析构函数:多态的安全网
有虚函数的类必须有虚析构函数,就像安全网,确保正确清理资源。
class Base {
public:
virtual ~Base() { // 虚析构函数
std::cout << "Base destructor" << std::endl;
}
};
class Derived : public Base {
public:
~Derived() {
std::cout << "Derived destructor" << std::endl;
}
};
Base* ptr = new Derived();
delete ptr; // 正确调用Derived的析构函数
现代C++的多态:更安全的魔法
C++11带来了更安全的魔法:
class Animal {
public:
virtual void eat() {}
virtual ~Animal() = default;
};
class Dog : public Animal {
public:
void eat() override { // override关键字,明确表示重写
std::cout << "Dog eating" << std::endl;
}
};
class FinalDog : public Dog {
public:
void eat() override final { // final关键字,禁止进一步重写
std::cout << "Final dog eating" << std::endl;
}
};
设计模式:多态的艺术
设计模式是多态的经典应用。它们展示了如何正确使用多态来解决实际问题。
- 如工厂模式、建造者模式
- 如适配器模式、装饰器模式
- 如策略模式、观察者模式
设计模式体现了面向对象的设计原则,如开闭原则、里氏替换原则等。
// 策略模式:不同的算法,相同的接口
class SortStrategy {
public:
virtual void sort(std::vector<int>& data) = 0;
virtual ~SortStrategy() = default;
};
class QuickSort : public SortStrategy {
public:
void sort(std::vector<int>& data) override {
std::cout << "Quick sort" << std::endl;
}
};
class MergeSort : public SortStrategy {
public:
void sort(std::vector<int>& data) override {
std::cout << "Merge sort" << std::endl;
}
};
class Sorter {
private:
SortStrategy* strategy;
public:
Sorter(SortStrategy* s) : strategy(s) {}
void sortData(std::vector<int>& data) {
strategy->sort(data);
}
};
性能考虑:多态的代价
多态有性能代价,就像魔法需要消耗法力:
这个比喻虽然不是很准确,但至少能让人理解个大概。
class Base {
public:
virtual void func() {} // 虚函数调用,有额外开销
void nonVirtualFunc() {} // 非虚函数调用,没有额外开销
};
常见陷阱:多态的陷阱
多态使用不当会导致性能问题和设计问题。
- 不必要的虚函数调用开销
- 内存泄漏风险
- 运行时开销
- 只在需要多态时使用
- 有虚函数的类必须有虚析构函数
- 模板、CRTP等
class Base {
public:
virtual void func() { std::cout << "Base" << std::endl; }
};
class Derived : public Base {
public:
void func() override { std::cout << "Derived" << std::endl; }
};
Derived d;
Base b = d; // 对象切片,丢失多态性
b.func(); // 输出 "Base"
写在最后
多态不是技术实现,而是设计思想。它体现了"一个接口,多种实现"的设计哲学。
- 多态让接口稳定,实现可以变化
- 多态基于行为的一致性,而不是数据的一致性
- 多态提供了良好的扩展性
- 虚函数表实现运行时多态
- RTTI提供类型安全
- 设计模式展示多态的正确使用
- 编译时多态(模板、CRTP)提供零开销抽象
多态不是万能的,它是表达行为一致性的工具。用好了,代码灵活、易扩展、易维护;用不好,就是性能杀手。
好了,今天就聊到这里。下次我们聊聊模板,看看怎么实现泛型编程。