如何实现后期多态?不同模板抽象类对象的容器调用方案
解决模板抽象类子类的后期多态统一调用问题
你的问题核心是:虽然把所有子类都挂到了Base基类下,但Base没有定义统一的打印接口,所以无法通过基类指针调用子类特有的printNum或printTwiceNum方法。下面给你三种可行的解决方案,你可以根据自己的场景选择:
方案一:给基类添加统一的print接口(最直接)
这种方式只需要给Base类增加一个纯虚的print方法,然后让每个子类实现这个方法,内部调用自己的打印逻辑。这样就可以通过基类指针统一调用了。
修改后的完整代码:
#include <iostream> #include <vector> #include <memory> #include <utility> class Base{ public: virtual ~Base(){}; virtual void print() const = 0; // 新增统一打印接口 }; template<typename T> class A : public Base{ public: A() : m_num(1.0) {}; virtual ~A() {}; virtual void printNum() const = 0; protected: T m_num; }; template<typename T> class B : public Base{ public: B() : m_num(2.0) {}; virtual ~B() {}; virtual void printTwiceNum() const = 0; protected: T m_num; }; class A_example : public A<int> { public: A_example() : A<int>() {}; void printNum() const { std::cout << m_num << "\n"; }; // 实现Base的print接口,内部调用自己的printNum void print() const override { printNum(); } }; class B_example : public B<int> { public: B_example() : B<int>() {}; void printTwiceNum() const { std::cout << 2*m_num << "\n"; }; // 实现Base的print接口,内部调用自己的printTwiceNum void print() const override { printTwiceNum(); } }; int main(){ std::vector<std::unique_ptr<Base>> v; v.emplace_back( new A_example() ); v.emplace_back( new B_example() ); // 遍历容器统一调用print for(const auto& ptr : v){ ptr->print(); } return 0; }
这种方案的优点是改动小、逻辑清晰,适合所有子类都可以抽象出统一操作的场景。
方案二:使用访问者模式(适合多操作场景)
如果你的子类后续需要支持多种不同的操作(不止打印),访问者模式会是更好的选择。它可以在不修改子类的前提下,扩展新的操作逻辑。
实现步骤如下:
- 定义访问者基类,包含对每个子类的访问方法
- 修改
Base类,添加accept纯虚方法 - 子类实现
accept方法,接受访问者并调用对应的访问逻辑 - 实现具体的访问者(比如打印访问者)
完整代码示例:
#include <iostream> #include <vector> #include <memory> #include <utility> // 前置声明子类,用于Visitor class A_example; class B_example; // 访问者基类 class Visitor { public: virtual ~Visitor() = default; virtual void visit(const A_example& obj) const = 0; virtual void visit(const B_example& obj) const = 0; // 后续新增子类时,在这里添加对应的visit方法 }; class Base{ public: virtual ~Base(){}; virtual void accept(const Visitor& visitor) const = 0; // 新增accept方法 }; template<typename T> class A : public Base{ public: A() : m_num(1.0) {}; virtual ~A() {}; virtual void printNum() const = 0; protected: T m_num; }; template<typename T> class B : public Base{ public: B() : m_num(2.0) {}; virtual ~B() {}; virtual void printTwiceNum() const = 0; protected: T m_num; }; class A_example : public A<int> { public: A_example() : A<int>() {}; void printNum() const { std::cout << m_num << "\n"; }; // 实现accept方法 void accept(const Visitor& visitor) const override { visitor.visit(*this); } }; class B_example : public B<int> { public: B_example() : B<int>() {}; void printTwiceNum() const { std::cout << 2*m_num << "\n"; }; // 实现accept方法 void accept(const Visitor& visitor) const override { visitor.visit(*this); } }; // 具体的打印访问者 class PrintVisitor : public Visitor { public: void visit(const A_example& obj) const override { obj.printNum(); } void visit(const B_example& obj) const override { obj.printTwiceNum(); } }; int main(){ std::vector<std::unique_ptr<Base>> v; v.emplace_back( new A_example() ); v.emplace_back( new B_example() ); PrintVisitor printer; // 遍历容器,通过访问者调用对应打印方法 for(const auto& ptr : v){ ptr->accept(printer); } return 0; }
这种方案的优点是扩展性强,新增操作只需要新增访问者类,不需要修改子类;缺点是需要维护访问者和子类的对应关系,子类增多时访问者类的方法也会增多。
方案三:使用std::variant + std::visit(C++17及以上)
如果你使用的是C++17或更高版本,可以不用基类,直接用std::variant来存储不同类型的对象,再通过std::visit来调用对应的打印方法。这种方式属于编译期多态,代码更简洁。
完整代码示例:
#include <iostream> #include <vector> #include <variant> template<typename T> class A{ public: A() : m_num(1.0) {}; virtual ~A() {}; virtual void printNum() const = 0; protected: T m_num; }; template<typename T> class B{ public: B() : m_num(2.0) {}; virtual ~B() {}; virtual void printTwiceNum() const = 0; protected: T m_num; }; class A_example : public A<int> { public: A_example() : A<int>() {}; void printNum() const { std::cout << m_num << "\n"; }; }; class B_example : public B<int> { public: B_example() : B<int>() {}; void printTwiceNum() const { std::cout << 2*m_num << "\n"; }; }; int main(){ // 用variant存储不同类型的子类对象 std::vector<std::variant<A_example, B_example>> v; v.emplace_back(A_example()); v.emplace_back(B_example()); // 用std::visit遍历,通过constexpr判断调用对应方法 for(const auto& var : v){ std::visit([](const auto& obj){ if constexpr (requires { obj.printNum(); }){ obj.printNum(); } else if constexpr (requires { obj.printTwiceNum(); }){ obj.printTwiceNum(); } }, var); } return 0; }
这种方案的优点是不需要基类,避免了运行时多态的开销;缺点是依赖C++17的特性,且新增子类时需要修改variant的模板参数。
内容的提问来源于stack exchange,提问作者Taylor
相关产品推荐
相关产品推荐

