More Effective C++ 条款31:让函数根据多个对象来决定怎么虚拟
核心思想:通过多重分发(Double Dispatch)和Visitor模式,实现基于多个对象类型的动态行为分发,解决单一虚拟函数机制无法处理的多态选择问题。
🚀 1. 问题本质分析
1.1 单一虚拟函数的局限性:
- 只能基于单个对象类型:虚函数根据this对象的动态类型进行分发
- 无法处理多对象交互:当行为取决于两个或多个对象的类型时,虚函数机制不足
- 组合爆炸问题:使用if-else或switch-case会导致代码冗余和难以维护
- 类型耦合度高:添加新类型需要修改所有相关函数
1.2 多重分发的核心需求:
- 动态双分发:根据两个对象的动态类型选择行为
- 扩展性:容易添加新类型和新操作
- 类型安全:编译时类型检查
- 解耦合:减少类型之间的相互依赖
// 基础示例:游戏对象碰撞检测问题
class GameObject;
class SpaceShip;
class SpaceStation;
class Asteroid;
// 基础游戏对象类
class GameObject {
public:
virtual ~GameObject() = default;
virtual void collide(GameObject& other) = 0;
// 需要为每个可能碰撞的类型提供接口
virtual void collideWith(SpaceShip& ship) = 0;
virtual void collideWith(SpaceStation& station) = 0;
virtual void collideWith(Asteroid& asteroid) = 0;
};
// 派生类需要实现所有碰撞组合
class SpaceShip : public GameObject {
public:
void collide(GameObject& other) override {
// 双重分发:让另一个对象处理碰撞
other.collideWith(*this);
}
void collideWith(SpaceShip& ship) override {
std::cout << "SpaceShip-SpaceShip collision\n";
}
void collideWith(SpaceStation& station) override {
std::cout << "SpaceShip-SpaceStation collision\n";
}
void collideWith(Asteroid& asteroid) override {
std::cout << "SpaceShip-Asteroid collision\n";
}
};
// 使用示例
void basicDoubleDispatch() {
SpaceShip ship1, ship2;
SpaceStation station;
Asteroid asteroid;
ship1.collide(ship2); // SpaceShip-SpaceShip collision
ship1.collide(station); // SpaceShip-SpaceStation collision
ship1.collide(asteroid); // SpaceShip-Asteroid collision
}
📦 2. 问题深度解析
2.1 手动实现双重分发:
// 更完善的双重分发实现
class SpaceShip;
class SpaceStation;
class Asteroid;
class Satellite; // 新类型
class GameObject {
public:
virtual ~GameObject() = default;
// 主要碰撞接口
virtual void collide(GameObject& other) = 0;
// 具体的碰撞处理函数
virtual void processCollision(SpaceShip& ship) = 0;
virtual void processCollision(SpaceStation& station) = 0;
virtual void processCollision(Asteroid& asteroid) = 0;
virtual void processCollision(Satellite& satellite) = 0;
};
// 实现基类提供默认碰撞处理
class DefaultCollisionHandler {
public:
static void handleUnknownCollision(GameObject& obj1, GameObject& obj2) {
std::cout << "Unknown collision between "
<< typeid(obj1).name() << " and "
<< typeid(obj2).name() << std::endl;
}
};
class SpaceShip : public GameObject {
public:
void collide(GameObject& other) override {
other.processCollision(*this);
}
void processCollision(SpaceShip& ship) override {
std::cout << "Two spaceships collide\n";
// 具体的碰撞处理逻辑
}
void processCollision(SpaceStation& station) override {
std::cout << "Spaceship docks with station\n";
}
void processCollision(Asteroid& asteroid) override {
std::cout << "Spaceship hit by asteroid!\n";
}
void processCollision(Satellite& satellite) override {
std::cout << "Spaceship collides with satellite\n";
}
};
// 新类型需要实现所有碰撞处理
class Satellite : public GameObject {
public:
void collide(GameObject& other) override {
other.processCollision(*this);
}
void processCollision(SpaceShip& ship) override {
std::cout << "Satellite hit by spaceship\n";
}
void processCollision(SpaceStation& station) override {
std::cout << "Satellite docks with station\n";
}
void processCollision(Asteroid& asteroid) override {
std::cout << "Satellite destroyed by asteroid\n";
}
void processCollision(Satellite& satellite) override {
std::cout << "Two satellites collide\n";
}
};
// 使用示例
void advancedDoubleDispatch() {
SpaceShip ship;
SpaceStation station;
Asteroid asteroid;
Satellite satellite;
// 自动选择正确的碰撞处理
ship.collide(station); // Spaceship docks with station
station.collide(asteroid); // 需要SpaceStation实现相应方法
satellite.collide(ship); // Satellite hit by spaceship
}
2.2 使用函数指针表实现分发:
// 基于映射表的多重分发
class GameObject;
// 碰撞处理函数类型
using CollisionHandler = void(*)(GameObject&, GameObject&);
// 全局碰撞处理映射表
class CollisionMap {
public:
using Key = std::pair<std::type_index, std::type_index>;
using Map = std::map<Key, CollisionHandler>;
static void addHandler(const std::type_info& type1,
const std::type_info& type2,
CollisionHandler handler) {
map_[Key(type1, type2)] = handler;
// 添加对称处理
map_[Key(type2, type1)] = handler;
}
static CollisionHandler getHandler(const std::type_info& type1,
const std::type_info& type2) {
auto it = map_.find(Key(type1, type2));
if (it != map_.end()) {
return it->second;
}
return nullptr;
}
private:
static Map map_;
};
// 初始化静态成员
CollisionMap::Map CollisionMap::map_;
// 简化版游戏对象
class GameObject {
public:
virtual ~GameObject() = default;
virtual std::type_index type() const = 0;
void collide(GameObject& other) {
auto handler = CollisionMap::getHandler(type(), other.type());
if (handler) {
handler(*this, other);
} else {
std::cout << "No handler for collision between "
<< type().name() << " and " << other.type().name() << std::endl;
}
}
};
// 具体的碰撞处理函数
void handleShipAsteroid(GameObject& go1, GameObject& go2) {
std::cout << "Handling spaceship-asteroid collision\n";
// 可以安全转换,因为我们知道类型
// SpaceShip& ship = static_cast<SpaceShip&>(go1);
// Asteroid& asteroid = static_cast<Asteroid&>(go2);
}
void handleShipStation(GameObject& go1, GameObject& go2) {
std::cout << "Handling spaceship-station docking\n";
}
// 注册碰撞处理
class CollisionRegistrar {
public:
CollisionRegistrar() {
CollisionMap::addHandler(typeid(SpaceShip), typeid(Asteroid), handleShipAsteroid);
CollisionMap::addHandler(typeid(SpaceShip), typeid(SpaceStation), handleShipStation);
}
};
// 静态注册器
static CollisionRegistrar registrar;
// 使用示例
void mapBasedDispatch() {
SpaceShip ship;
Asteroid asteroid;
SpaceStation station;
ship.collide(asteroid); // Handling spaceship-asteroid collision
ship.collide(station); // Handling spaceship-station docking
GameObject* unknown = new SpaceShip;
asteroid.collide(*unknown); // 同样有效
delete unknown;
}
⚖️ 3. 解决方案与最佳实践
3.1 Visitor模式实现多重分发:
// 使用Visitor模式实现优雅的多重分发
class SpaceShip;
class SpaceStation;
class Asteroid;
class Satellite;
// 前向声明
class GameObjectVisitor;
// 可接受访问者的游戏对象
class GameObject {
public:
virtual ~GameObject() = default;
virtual void accept(GameObjectVisitor& visitor) = 0;
virtual void collide(GameObject& other) = 0;
};
// 访问者接口
class GameObjectVisitor {
public:
virtual ~GameObjectVisitor() = default;
virtual void visit(SpaceShip& ship) = 0;
virtual void visit(SpaceStation& station) = 0;
virtual void visit(Asteroid& asteroid) = 0;
virtual void visit(Satellite& satellite) = 0;
};
// 碰撞访问者
class CollisionVisitor : public GameObjectVisitor {
public:
CollisionVisitor(GameObject& collider) : collider_(collider) {}
void visit(SpaceShip& ship) override {
handleCollision(ship, collider_);
}
void visit(SpaceStation& station) override {
handleCollision(station, collider_);
}
void visit(Asteroid& asteroid) override {
handleCollision(asteroid, collider_);
}
void visit(Satellite& satellite) override {
handleCollision(satellite, collider_);
}
private:
template<typename T1, typename T2>
void handleCollision(T1& obj1, T2& obj2) {
std::cout << "Collision between " << typeid(T1).name()
<< " and " << typeid(T2).name() << std::endl;
}
GameObject& collider_;
};
// 具体游戏对象实现
class SpaceShip : public GameObject {
public:
void accept(GameObjectVisitor& visitor) override {
visitor.visit(*this);
}
void collide(GameObject& other) override {
CollisionVisitor visitor(*this);
other.accept(visitor);
}
};
class SpaceStation : public GameObject {
public:
void accept(GameObjectVisitor& visitor) override {
visitor.visit(*this);
}
void collide(GameObject& other) override {
CollisionVisitor visitor(*this);
other.accept(visitor);
}
};
// 使用示例
void visitorPatternExample() {
SpaceShip ship;
SpaceStation station;
Asteroid asteroid;
ship.collide(station); // 通过Visitor处理碰撞
station.collide(asteroid);
}
3.2 使用std::variant和std::visit(C++17):
// 现代C++17实现多重分发
#include <variant>
#include <vector>
// 游戏对象类型
class SpaceShip {
public:
void collideWith(SpaceShip&) { std::cout << "Ship-Ship\n"; }
void collideWith(class SpaceStation&) { std::cout << "Ship-Station\n"; }
void collideWith(class Asteroid&) { std::cout << "Ship-Asteroid\n"; }
};
class SpaceStation {
public:
void collideWith(SpaceShip&) { std::cout << "Station-Ship\n"; }
void collideWith(SpaceStation&) { std::cout << "Station-Station\n"; }
void collideWith(class Asteroid&) { std::cout << "Station-Asteroid\n"; }
};
class Asteroid {
public:
void collideWith(SpaceShip&) { std::cout << "Asteroid-Ship\n"; }
void collideWith(SpaceStation&) { std::cout << "Asteroid-Station\n"; }
void collideWith(Asteroid&) { std::cout << "Asteroid-Asteroid\n"; }
};
// 使用variant包装所有类型
using GameObject = std::variant<SpaceShip, SpaceStation, Asteroid>;
// 碰撞访问器
struct CollisionHandler {
template<typename T1, typename T2>
void operator()(T1& obj1, T2& obj2) {
obj1.collideWith(obj2);
}
};
// 碰撞函数
void processCollision(GameObject& obj1, GameObject& obj2) {
std::visit(CollisionHandler{}, obj1, obj2);
}
// 使用示例
void variantBasedDispatch() {
GameObject ship = SpaceShip();
GameObject station = SpaceStation();
GameObject asteroid = Asteroid();
processCollision(ship, station); // Ship-Station
processCollision(ship, asteroid); // Ship-Asteroid
processCollision(station, asteroid); // Station-Asteroid
// 可以存储在容器中
std::vector<GameObject> objects{ship, station, asteroid};
processCollision(objects[0], objects[1]);
}
3.3 类型安全的动态分发框架:
// 类型安全的双重分发框架
template<typename Base, typename... Deriveds>
class DoubleDispatcher {
public:
using Handler = std::function<void(Base&, Base&)>;
template<typename T1, typename T2>
void registerHandler(Handler handler) {
auto key = std::make_pair(typeid(T1), typeid(T2));
handlers_[key] = handler;
// 注册对称处理
auto symmetricKey = std::make_pair(typeid(T2), typeid(T1));
handlers_[symmetricKey] = [handler](Base& a, Base& b) {
handler(b, a); // 交换参数
};
}
bool dispatch(Base& obj1, Base& obj2) {
auto key = std::make_pair(typeid(obj1), typeid(obj2));
auto it = handlers_.find(key);
if (it != handlers_.end()) {
it->second(obj1, obj2);
return true;
}
return false;
}
private:
std::map<std::pair<std::type_index, std::type_index>, Handler> handlers_;
};
// 使用框架
class GameEntity {
public:
virtual ~GameEntity() = default;
};
class Player : public GameEntity {};
class Enemy : public GameEntity {};
class Projectile : public GameEntity {};
void setupDispatcher(DoubleDispatcher<GameEntity>& dispatcher) {
dispatcher.registerHandler<Player, Enemy>([](GameEntity& p, GameEntity& e) {
std::cout << "Player hits Enemy\n";
});
dispatcher.registerHandler<Projectile, Enemy>([](GameEntity& proj, GameEntity& e) {
std::cout << "Projectile hits Enemy\n";
});
dispatcher.registerHandler<Player, Projectile>([](GameEntity& p, GameEntity& proj) {
std::cout << "Player hits Projectile\n";
});
}
// 使用示例
void frameworkExample() {
DoubleDispatcher<GameEntity> dispatcher;
setupDispatcher(dispatcher);
Player player;
Enemy enemy;
Projectile projectile;
dispatcher.dispatch(player, enemy); // Player hits Enemy
dispatcher.dispatch(projectile, enemy); // Projectile hits Enemy
dispatcher.dispatch(player, projectile); // Player hits Projectile
}
💡 关键实践原则
- 选择适当的分发机制
- 简单情况:手动双重分发
- 中等复杂度:Visitor模式
- 现代C++:std::variant + std::visit
- 大型系统:自定义分发框架
- 考虑扩展性
- 添加新类型时的影响范围
- 新操作的添加难易程度
- 性能考量
- 虚函数调用 vs 函数指针查找 vs 编译时分发
- 缓存友好性
- 类型安全
- 避免运行时类型错误
- 提供合理的默认行为
- 代码组织
- 集中管理分发逻辑
- 良好的错误处理和日志记录
应用场景总结:
// 1. 游戏开发:碰撞检测、技能效果 // 2. GUI系统:事件处理、控件交互 // 3. 编译器:AST节点处理 // 4. 数学库:表达式求值 // 5. 业务逻辑:多类型实体交互
C++中的多重分发技术演进:
// 传统方法:虚函数 + RTTI // 设计模式:Visitor模式 // 现代方法:std::variant + std::visit // 高级技术:模板元编程 + 概念约束 void evolutionExample() { // 传统方法(容易产生组合爆炸) // 现代方法(类型安全,易于扩展) // 未来方向(编译时多分发) }
总结:
多重分发是解决基于多个对象类型进行行为选择的强大技术,突破了C++单分发的限制。
从传统的手动双重分发到现代的std::visit方案,C++提供了多种实现方式。选择合适的方法需要考虑系统的复杂度、性能要求、扩展性需求和团队的技术水平。
良好的多重分发设计可以显著提高代码的可维护性和扩展性,特别是在处理复杂对象交互的场景中。现代C++特性如variant和visit为这个问题提供了更优雅、类型安全的解决方案。