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

无需继承非模板基类,如何在非模板类中存储任意模板类?

不用非模板基类存储任意模板类的几种方法

当然有啦!除了你现在用的继承非模板基类的方式,C++里还有不少不用基类就能存储任意模板类实例的方案,我给你逐个拆解:

1. 使用std::any(C++17+)

std::any是C++标准库提供的类型安全的任意类型容器,它可以存储任意类型的对象,完全不需要依赖基类继承。

示例代码:

#include <any>
#include <iostream>

template <typename... Types>
class ExtendedA {
public:
    void print() const {
        std::cout << "ExtendedA with " << sizeof...(Types) << " template args\n";
    }
};

class Storage {
public:
    std::any toStore;
};

int main() {
    Storage a;
    // 存储不同模板实例的对象
    a.toStore = ExtendedA<int, char>();
    a.toStore = ExtendedA<double>();

    // 取出对象时需要明确类型,用std::any_cast
    if (auto* ptr = std::any_cast<ExtendedA<double>>(&a.toStore)) {
        ptr->print(); // 输出: ExtendedA with 1 template args
    }
}

优点:

  • 完全灵活,能存任何类型,不用提前规划类型范围
  • 标准库原生支持,无需手动实现复杂逻辑

缺点:

  • 取出时必须知道具体类型,否则会抛出异常(或返回空指针)
  • 相比继承方式,有一定的类型擦除开销

2. 使用std::variant(C++17+)

如果你能提前确定要存储的模板实例类型范围,std::variant会是更类型安全、更高效的选择——它是一个类型安全的联合体,只能存储预先指定的类型集合。

示例代码:

#include <variant>
#include <iostream>
#include <string>

template <typename... Types>
class ExtendedA {
public:
    void print() const {
        std::cout << "ExtendedA with " << sizeof...(Types) << " template args\n";
    }
};

// 预先定义允许存储的模板实例类型
using ExtendedAVariant = std::variant<ExtendedA<int>, ExtendedA<char, std::string>, ExtendedA<double>>;

class Storage {
public:
    ExtendedAVariant toStore;
};

int main() {
    Storage a;
    a.toStore = ExtendedA<char, std::string>();

    // 用std::visit访问variant中的对象
    std::visit([](const auto& obj) {
        obj.print(); // 输出: ExtendedA with 2 template args
    }, a.toStore);
}

优点:

  • 类型安全,编译时就能检查非法类型
  • 性能比std::any好,无额外堆分配(除非存储的类型本身需要)
  • 访问对象时无需手动判断类型,std::visit会自动分发

缺点:

  • 必须提前枚举所有可能存储的模板实例类型,灵活性不如std::any

3. 手动实现无基类的类型擦除

如果你的项目还不能用C++17,或者需要更底层的控制,可以手动实现类型擦除——用void*存储对象指针,再搭配函数指针封装对象的核心操作(比如销毁、复制、调用方法等),完全不需要继承基类。

示例代码:

#include <iostream>

template <typename... Types>
class ExtendedA {
public:
    void print() const {
        std::cout << "ExtendedA with " << sizeof...(Types) << " template args\n";
    }
};

class Storage {
private:
    void* objPtr = nullptr;
    // 封装销毁对象的函数指针
    void (*destroyFn)(void*) = nullptr;
    // 封装print操作的函数指针
    void (*printFn)(const void*) = nullptr;

public:
    // 模板构造函数,接受任意类型的对象
    template <typename T>
    Storage(T&& obj) {
        objPtr = new T(std::forward<T>(obj));
        destroyFn = [](void* ptr) { delete static_cast<T*>(ptr); };
        printFn = [](const void* ptr) { static_cast<const T*>(ptr)->print(); };
    }

    // 禁止拷贝(如需拷贝可添加拷贝构造和赋值,封装拷贝函数指针)
    Storage(const Storage&) = delete;
    Storage& operator=(const Storage&) = delete;

    // 移动构造/赋值
    Storage(Storage&& other) noexcept : objPtr(other.objPtr), destroyFn(other.destroyFn), printFn(other.printFn) {
        other.objPtr = nullptr;
        other.destroyFn = nullptr;
        other.printFn = nullptr;
    }

    Storage& operator=(Storage&& other) noexcept {
        if (this != &other) {
            if (destroyFn) destroyFn(objPtr);
            objPtr = other.objPtr;
            destroyFn = other.destroyFn;
            printFn = other.printFn;
            other.objPtr = nullptr;
            other.destroyFn = nullptr;
            other.printFn = nullptr;
        }
        return *this;
    }

    ~Storage() {
        if (destroyFn) destroyFn(objPtr);
    }

    // 对外暴露操作接口
    void print() const {
        if (printFn) printFn(objPtr);
    }
};

int main() {
    Storage a = ExtendedA<int, char>();
    a.print(); // 输出: ExtendedA with 2 template args

    Storage b = ExtendedA<double>();
    b.print(); // 输出: ExtendedA with 1 template args
}

优点:

  • 不依赖C++17特性,兼容旧版本编译器
  • 完全自定义操作逻辑,开销可控
  • 无需修改原模板类的结构

缺点:

  • 需要手动管理内存和函数指针,代码量较大
  • 逻辑复杂,容易出现内存泄漏或类型错误

对比你原来的继承方案

你原来的继承基类方案,本质是利用C++多态实现类型擦除,优点是符合面向对象设计习惯,缺点是必须给基类添加虚函数、所有派生类都要遵循基类接口,且虚函数调用有轻微运行时开销。上面的几种方案则摆脱了基类约束,各有适用场景——如果追求灵活选std::any,追求类型安全选std::variant,兼容旧环境则选手动类型擦除。

内容的提问来源于stack exchange,提问作者Donut

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 09:06:14