You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现后期多态?不同模板抽象类对象的容器调用方案

解决模板抽象类子类的后期多态统一调用问题

你的问题核心是:虽然把所有子类都挂到了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;
}

这种方案的优点是改动小、逻辑清晰,适合所有子类都可以抽象出统一操作的场景。

方案二:使用访问者模式(适合多操作场景)

如果你的子类后续需要支持多种不同的操作(不止打印),访问者模式会是更好的选择。它可以在不修改子类的前提下,扩展新的操作逻辑。

实现步骤如下:

  1. 定义访问者基类,包含对每个子类的访问方法
  2. 修改Base类,添加accept纯虚方法
  3. 子类实现accept方法,接受访问者并调用对应的访问逻辑
  4. 实现具体的访问者(比如打印访问者)

完整代码示例:

#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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 10:09:50