C++:在基类指针函数中向下转型至未知派生类的实现方案
问题描述
我有多个结构不同但都继承自基类DataBase的自定义数据类CustomData1到CustomDataN,还有一个Controller类需要通过doSomething()方法处理传入的数据。现在的问题是要把基类指针向下转型为对应的派生类指针,调用匹配的process()函数,但我不想写一堆带dynamic_cast的if分支(因为可能有大量数据结构)。
我试过用基类里的getPtr()方法实现正确向下转型,也尝试过类型擦除模式但没搞懂怎么用在这个场景里,求正确的实现方式和适用的设计模式。
示例代码
#include <iostream> #include <memory> // base class class DataBase { public: DataBase() { } virtual ~DataBase() { } // method to get derived class pointer (downcast) virtual DataBase* getPtr() = 0; }; template<class T> class DataAny : public DataBase { public: DataAny() : DataBase() { } virtual ~DataAny() {} // downcast to derived class T* getPtr() override { dynamic_cast<T*>(this); } }; class CustomData1 : public DataAny<CustomData1> { int _i; public: CustomData1() : DataAny<CustomData1>() , _i(1) { } int i() { return _i; } }; class CustomData2 : public DataAny<CustomData2> { std::string _s; public: CustomData2() : DataAny<CustomData2>() , _s("something") { } std::string s() { return _s; } }; class CustomData3 : public DataAny<CustomData3> { double _d; public: CustomData3() : DataAny<CustomData3>() , _d(10.0001) { } double d() { return _d; } }; class Controller { public: Controller() { } virtual ~Controller() { } bool doSomething(DataBase* data) { if (data == nullptr) return false; auto ptr = data->getPtr(); // I want to avoid this auto d1 = dynamic_cast<CustomData1*>(data); if (d1 != nullptr) { return process(d1); } auto d2 = dynamic_cast<CustomData2*>(data); if (d2 != nullptr) { return process(d2); } auto d3 = dynamic_cast<CustomData3*>(data); if (d3 != nullptr) { return process(d3); } // ... //auto d100 = dynamic_cast<CustomData100*>(data); //if (d100 != nullptr) //{ // return process(d100); //} // and make one universal function call but compilation error here process(data); return false; } private: bool process(CustomData1* data1) { std::cout << "CustomData1 i=" << data1->i() << std::endl; return true; } bool process(CustomData2* data1) { std::cout << "CustomData2 s=" << data1->s().c_str() << std::endl; return true; } bool process(CustomData3* data1) { std::cout << "CustomData3 d=" << data1->d() << std::endl; return true; } }; int main() { // make data auto obj1 = std::make_unique<CustomData1>(); auto obj2 = std::make_unique<CustomData2>(); auto obj3 = std::make_unique<CustomData3>(); Controller contr; contr.doSomething(obj3.get()); contr.doSomething(obj2.get()); contr.doSomething(obj1.get()); }
解决方案
针对这种多态分发场景,推荐两种成熟的实现方式:
方案一:访问者模式(Visitor Pattern)
核心是让数据类主动接受"访问者",把自身类型暴露给处理逻辑,彻底避免dynamic_cast分支。
修改后完整代码
#include <iostream> #include <memory> // 前向声明访问者接口 class DataVisitor; // 基类 class DataBase { public: virtual ~DataBase() = default; // 接受访问者的纯虚方法 virtual void accept(DataVisitor& visitor) = 0; }; // 访问者接口:每个数据类对应一个visit方法 class DataVisitor { public: virtual ~DataVisitor() = default; virtual bool visit(CustomData1* data) = 0; virtual bool visit(CustomData2* data) = 0; virtual bool visit(CustomData3* data) = 0; // 新增数据类时,在此添加对应的visit方法 }; class CustomData1 : public DataBase { int _i = 1; public: int i() const { return _i; } void accept(DataVisitor& visitor) override { visitor.visit(this); } }; class CustomData2 : public DataBase { std::string _s = "something"; public: const std::string& s() const { return _s; } void accept(DataVisitor& visitor) override { visitor.visit(this); } }; class CustomData3 : public DataBase { double _d = 10.0001; public: double d() const { return _d; } void accept(DataVisitor& visitor) override { visitor.visit(this); } }; class Controller : public DataVisitor { public: bool doSomething(DataBase* data) { if (!data) return false; // 让数据类主动调用访问者的对应处理方法 data->accept(*this); return true; } private: bool visit(CustomData1* data) override { std::cout << "CustomData1 i=" << data->i() << std::endl; return true; } bool visit(CustomData2* data) override { std::cout << "CustomData2 s=" << data->s() << std::endl; return true; } bool visit(CustomData3* data) override { std::cout << "CustomData3 d=" << data->d() << std::endl; return true; } }; int main() { auto obj1 = std::make_unique<CustomData1>(); auto obj2 = std::make_unique<CustomData2>(); auto obj3 = std::make_unique<CustomData3>(); Controller contr; contr.doSomething(obj3.get()); contr.doSomething(obj2.get()); contr.doSomething(obj1.get()); }
优势
- 无需手动编写转型分支,新增数据类仅需两步:在访问者接口加
visit方法、在数据类实现accept - 处理逻辑集中在访问者类,数据类只负责暴露自身类型,符合单一职责原则
方案二:基于CRTP的编译期分发
利用奇异递归模板模式(CRTP)在编译期自动完成类型转换和分发,比dynamic_cast效率更高,代码更简洁。
修改后完整代码
#include <iostream> #include <memory> // 前向声明Controller class Controller; class DataBase { public: virtual ~DataBase() = default; // 纯虚分发方法,由CRTP基类实现 virtual bool dispatch(Controller& controller) = 0; }; // CRTP基类:自动实现dispatch方法,完成类型转发 template<typename Derived> class DataCRTP : public DataBase { public: bool dispatch(Controller& controller) override; }; class Controller { public: bool doSomething(DataBase* data) { if (!data) return false; // 调用CRTP实现的分发方法,自动匹配对应process return data->dispatch(*this); } // 各个数据类的处理函数 bool process(CustomData1* data) { std::cout << "CustomData1 i=" << data->i() << std::endl; return true; } bool process(CustomData2* data) { std::cout << "CustomData2 s=" << data->s() << std::endl; return true; } bool process(CustomData3* data) { std::cout << "CustomData3 d=" << data->d() << std::endl; return true; } }; // 实现CRTP的dispatch方法:编译期转型为Derived*并调用process template<typename Derived> bool DataCRTP<Derived>::dispatch(Controller& controller) { return controller.process(static_cast<Derived*>(this)); } // 自定义数据类只需继承CRTP基类 class CustomData1 : public DataCRTP<CustomData1> { int _i = 1; public: int i() const { return _i; } }; class CustomData2 : public DataCRTP<CustomData2> { std::string _s = "something"; public: const std::string& s() const { return _s; } }; class CustomData3 : public DataCRTP<CustomData3> { double _d = 10.0001; public: double d() const { return _d; } }; int main() { auto obj1 = std::make_unique<CustomData1>(); auto obj2 = std::make_unique<CustomData2>(); auto obj3 = std::make_unique<CustomData3>(); Controller contr; contr.doSomething(obj3.get()); contr.doSomething(obj2.get()); contr.doSomething(obj1.get()); }
优势
- 自定义数据类代码极简,仅需继承
DataCRTP<Derived>,无需手动实现任何分发逻辑 - 编译期完成类型转换,无运行时开销,比
dynamic_cast效率更高 - 新增数据类仅需添加对应的
process函数和继承CRTP基类,无需修改基类接口
方案对比
- 访问者模式更适合处理逻辑频繁变化或需要多套处理逻辑(多个访问者)的场景
- CRTP方案更适合数据类结构稳定、处理逻辑相对固定的场景,编译期分发效率更高
内容的提问来源于stack exchange,提问作者Pasha
相关产品推荐
相关产品推荐

