如何将不同模板参数的模板类实例存入std::vector?
哈哈,这个坑我之前踩过!Foo<1>和Foo<2>虽然内存布局一模一样,但在编译器眼里就是完全不搭边的两个类型,直接塞vector肯定编译不过。给你几个实用的解决思路,按需选就行:
核心思路是给所有Foo<N>套一个共同的非模板基类,然后用vector存储基类的指针(推荐用智能指针避免内存泄漏)。
// 先定义一个非模板基类,作为所有Foo实例的统一接口 class FooBase { public: virtual ~FooBase() = default; // 必须加虚析构,否则销毁时会漏析构子类 // 如果需要对实例做统一操作,可以在这里定义虚函数接口 // 比如 virtual void doSomething() = 0; }; // 让模板类Foo继承自FooBase template<int p1> class Foo : public FooBase { public: Foo() : i(p1) {} private: int i; }; // 用vector存储智能指针,自动管理内存 std::vector<std::unique_ptr<FooBase>> foo_vector; foo_vector.push_back(std::make_unique<Foo<1>>()); foo_vector.push_back(std::make_unique<Foo<2>>());
这个方案的优点是类型安全,完全符合C++的面向对象设计规范;缺点是因为虚函数的存在,会有一点点性能开销(可以忽略不计,除非你在极端性能敏感的场景)。如果需要访问Foo<N>的具体成员,可以用dynamic_cast做类型转换,但要注意检查转换是否成功。
如果你用的是C++17或更高版本,std::variant是个更轻量的选择——它可以把多个不同类型打包成一个变体类型,然后vector存储这个变体。
#include <variant> // 先定义一个variant类型,包含你需要的所有Foo实例类型 using FooVariant = std::variant<Foo<1>, Foo<2>>; // 直接存variant到vector里 std::vector<FooVariant> foo_vector; foo_vector.push_back(Foo<1>()); foo_vector.push_back(Foo<2>());
访问的时候需要用std::visit来处理不同的类型:
// 遍历vector,处理每个Foo实例 for (auto& var : foo_vector) { std::visit([](auto&& foo) { // 这里的foo会自动推导为对应的Foo<1>或Foo<2>类型 // 可以在这里执行针对Foo的操作 }, var); }
这个方案的优点是没有虚函数的性能开销,代码也更简洁;缺点是需要提前枚举所有可能的Foo<N>类型,如果模板参数的范围不确定或者太多,就不太方便了。
你提到所有Foo<N>的内存布局相同,理论上可以通过直接操作内存来“欺骗”编译器,但这个方法非常危险,强烈不推荐,除非你100%确定内存布局永远不会变,并且愿意承担未定义行为的风险。
#include <cstring> #include <vector> #include <cstddef> std::vector<std::byte> foo_buffer; // 存入Foo<1>实例 Foo<1> f1; foo_buffer.resize(foo_buffer.size() + sizeof(Foo<1>)); std::memcpy(foo_buffer.data() + foo_buffer.size() - sizeof(Foo<1>), &f1, sizeof(Foo<1>)); // 存入Foo<2>实例 Foo<2> f2; foo_buffer.resize(foo_buffer.size() + sizeof(Foo<2>)); std::memcpy(foo_buffer.data() + foo_buffer.size() - sizeof(Foo<2>), &f2, sizeof(Foo<2>)); // 取出时强制转换类型(必须记住每个实例的类型和位置) Foo<1>* pf1 = reinterpret_cast<Foo<1>*>(foo_buffer.data()); Foo<2>* pf2 = reinterpret_cast<Foo<2>*>(foo_buffer.data() + sizeof(Foo<1>));
一旦Foo的定义发生变化(比如加了成员变量、虚函数),或者编译器的内存布局优化改变,这个代码立刻会出问题,可能导致崩溃或奇怪的行为,所以不到万不得已别用。
总结一下:优先选基类+多态或者std::variant,这两个都是类型安全且符合C++规范的方案;内存强制转换只适合极端应急场景。
内容的提问来源于stack exchange,提问作者user2269707

