C++基类指针调用重载派生类函数未匹配正确重载的问题求解
问题描述
以下是简化示例代码:
#include <iostream> using namespace std; class Base { public: virtual ~Base() {} virtual int test(Base* parent) = 0; }; class Test : public Base { public: ~Test() {} int test(Base* parent) { return 10; } int test(Test* parent) { return 20; } }; int main(int argc, char* argv[]) { Test* test = new Test(); Base* base = test; cout << test->test(test) << endl; // 输出20 cout << base->test(test) << endl; // 输出10 return 0; }
原本预期两次调用都返回20(因为实参类型是Test*),但第二次调用返回了10。
已知可以通过dynamic_cast<Test*>(base)->test(test)实现预期效果,但项目中有多个继承自Base的派生类。如果采用逐个dynamic_cast判断的写法:
auto test1 = dynamic_cast<Test*>(base); if (test1) { test1->test(test); } auto test2 = dynamic_cast<Test2*>(base); if (test2) { test2->test(test); } ...
这种方式在新增Base的派生类时,需要修改多处代码,维护成本极高。
请问是否存在一种方式,可以保留类似base->test(test)的调用形式,根据实参类型匹配到正确的重载函数?
解决方案
这是典型的双重分发问题:C的虚函数仅能根据调用者(this指针)的实际类型进行单分发,无法同时依据实参的实际类型匹配重载。要实现同时根据调用者和实参的实际类型选择对应函数,需要使用双重分发机制,最常用的实现是访客模式,也可以用C17的std::variant简化实现。
方案一:访客模式
实现思路
- 定义访客接口,为每个
Base的派生类声明对应的访问方法; - 在
Base中添加接受访客的纯虚函数; - 每个派生类实现
accept方法,主动调用访客中对应自身类型的方法; - 将原有的
test逻辑迁移到访客类中,针对调用者和实参的类型组合实现具体逻辑。
代码示例
#include <iostream> using namespace std; // 前向声明 class Test; class Test2; class BaseVisitor; class Base { public: virtual ~Base() {} // 接受访客的纯虚函数 virtual void accept(BaseVisitor* visitor) = 0; }; // 访客接口:为每个派生类定义访问方法 class BaseVisitor { public: virtual ~BaseVisitor() {} virtual void visit(Test* obj) = 0; virtual void visit(Test2* obj) = 0; }; class Test : public Base { public: ~Test() {} void accept(BaseVisitor* visitor) override { visitor->visit(this); } // 保留原有重载逻辑 int test(Test* parent) { return 20; } int test(Base* parent) { return 10; } }; class Test2 : public Base { public: ~Test2() {} void accept(BaseVisitor* visitor) override { visitor->visit(this); } int test(Test2* parent) { return 30; } int test(Base* parent) { return 15; } }; // 具体访客:处理test的双重分发逻辑 class TestVisitor : public BaseVisitor { private: Base* arg; int result; public: TestVisitor(Base* a) : arg(a), result(0) {} void visit(Test* caller) override { // 针对调用者是Test*的情况,再根据实参类型分发 struct ArgVisitor : BaseVisitor { Test* caller; int& res; ArgVisitor(Test* c, int& r) : caller(c), res(r) {} void visit(Test* obj) override { res = caller->test(obj); } void visit(Test2* obj) override { res = caller->test(static_cast<Base*>(obj)); } }; ArgVisitor argVisitor(caller, result); arg->accept(&argVisitor); } void visit(Test2* caller) override { struct ArgVisitor : BaseVisitor { Test2* caller; int& res; ArgVisitor(Test2* c, int& r) : caller(c), res(r) {} void visit(Test* obj) override { res = caller->test(static_cast<Base*>(obj)); } void visit(Test2* obj) override { res = caller->test(obj); } }; ArgVisitor argVisitor(caller, result); arg->accept(&argVisitor); } int getResult() const { return result; } }; // 封装成简洁的调用形式 int test(Base* caller, Base* arg) { TestVisitor visitor(arg); caller->accept(&visitor); return visitor.getResult(); } int main(int argc, char* argv[]) { Test* test = new Test(); Base* base = test; Test2* test2 = new Test2(); cout << test->test(test) << endl; // 输出20 cout << test(base, test) << endl; // 输出20(符合预期) cout << test(test2, test2) << endl; // 输出30 cout << test(test2, test) << endl; // 输出15 delete test; delete test2; return 0; }
优势
- 新增派生类时,仅需在
BaseVisitor中添加对应visit方法、在新类中实现accept、并在TestVisitor中补充对应分支,无需修改所有调用点; - 调用形式简洁(
test(base, test)),接近原有的base->test(test)写法。
方案二:C++17 std::variant简化实现
如果项目支持C++17及以上,可以用std::variant和std::visit替代访客模式,代码更简洁:
#include <iostream> #include <variant> using namespace std; // 基类声明 class Base {}; class Test; class Test2; // 用variant封装所有派生类指针 using BaseVariant = variant<Test*, Test2*>; class Test : public Base { public: int test(Test* parent) { return 20; } int test(Base* parent) { return 10; } }; class Test2 : public Base { public: int test(Test2* parent) { return 30; } int test(Base* parent) { return 15; } }; // 双重分发逻辑:通过std::visit处理variant组合 int test(BaseVariant caller, BaseVariant arg) { return visit([&](auto* c) { return visit([&](auto* a) { if constexpr (is_same_v<decltype(c), decltype(a)>) { return c->test(a); } else { return c->test(static_cast<Base*>(a)); } }, arg); }, caller); } int main() { Test* test = new Test(); Test2* test2 = new Test2(); cout << test(test, test) << endl; // 输出20 cout << test(BaseVariant(test), BaseVariant(test)) << endl; // 输出20(符合预期) cout << test(test2, test2) << endl; // 输出30 cout << test(test2, test) << endl; // 输出15 delete test; delete test2; return 0; }
优势
- 代码量更少,无需定义大量访客类;
- 新增派生类时,仅需更新
BaseVariant的定义和test函数中的分支逻辑。
内容的提问来源于stack exchange,提问作者tbraun89
相关产品推荐
相关产品推荐

