多可能性下的C++模板推导问题及Wrapper类设计咨询
多态Wrapper相关问题解答
原始代码
struct Wrapper { vector<shared_ptr<BaseType>> data; template <typename DerivedType> add(const DerivedType& e) { data.push_back(make_shared<DerivedType>(e)); }; }
该Wrapper的设计目标是实现多态,希望能将存入的任意对象转换回其原始DerivedType。现有派生自A的类B,执行wrapper.add(B());时,模板推导会将DerivedType解析为B。
问题1:由于B()的基类是A,模板推导是否也能将DerivedType解析为A(即使A并非其最派生类型)?
默认情况下不行。模板参数推导完全基于传入实参的实际类型,你传的是B(),实参类型就是B,所以编译器只会推导DerivedType为B。如果要让推导结果是A,要么显式把实参转成A类型(比如wrapper.add(static_cast<A>(B()));),要么直接显式指定模板参数(wrapper.add<A>(B());),否则编译器不会自动“降级”到基类类型。
问题2:如果可以,make_shared<A>(e)生成的shared_ptr能否被动态转换为shared_ptr?
绝对不行。make_shared<A>(e)会创建一个纯A类型的对象——传入的B对象会被“切片”,只保留基类A的部分,内存里根本没有B的成员数据和虚表信息。dynamic_pointer_cast<B>要求被转换的指针实际指向B类型的对象,否则会返回空指针,这里显然不满足条件,转换必然失败。
问题3:如果不行,是否有更优的Wrapper设计方案?要求Wrapper可复制,我希望避免显式指定模板参数。
可以通过类型擦除的方式实现,核心是让Wrapper存储的对象“记住”自己的原始类型,同时支持复制操作。这里提供两种可行方案:
方案1:无基类依赖的类型擦除Wrapper
适合不需要统一基类的场景,直接存储任意类型的对象,并支持转换回原始类型:
#include <vector> #include <memory> struct AnyHolder { virtual ~AnyHolder() = default; virtual std::unique_ptr<AnyHolder> clone() const = 0; // 支持Wrapper复制 virtual void* get() = 0; }; template <typename T> struct Holder : AnyHolder { T value; Holder(const T& v) : value(v) {} std::unique_ptr<AnyHolder> clone() const override { return std::make_unique<Holder<T>>(value); } void* get() override { return &value; } }; struct Wrapper { std::vector<std::unique_ptr<AnyHolder>> data; template <typename DerivedType> void add(const DerivedType& e) { data.push_back(std::make_unique<Holder<DerivedType>>(e)); } // 转换回原始类型,返回nullptr表示索引越界或类型不匹配 template <typename T> T* get(size_t index) { if (index >= data.size()) return nullptr; return static_cast<T*>(data[index]->get()); } // 复制构造函数 Wrapper(const Wrapper& other) { for (const auto& holder : other.data) { data.push_back(holder->clone()); } } // 复制赋值运算符 Wrapper& operator=(const Wrapper& other) { if (this != &other) { data.clear(); for (const auto& holder : other.data) { data.push_back(holder->clone()); } } return *this; } // 移动构造和赋值默认生成 Wrapper(Wrapper&&) = default; Wrapper& operator=(Wrapper&&) = default; };
使用示例:
Wrapper w; w.add(B()); B* b_ptr = w.get<B>(0); // 直接拿到B类型指针
方案2:保留多态基类的类型擦除Wrapper
如果需要保留A作为统一基类,同时支持转换回原始派生类型,可以结合type_index做类型校验:
#include <vector> #include <memory> #include <typeindex> struct A { virtual ~A() = default; }; struct B : A {}; struct AnyHolder { virtual ~AnyHolder() = default; virtual std::unique_ptr<AnyHolder> clone() const = 0; virtual std::shared_ptr<A> get_base() const = 0; virtual std::type_index type() const = 0; virtual void* get_raw() const = 0; }; template <typename T> struct Holder : AnyHolder { std::shared_ptr<T> value; Holder(const T& v) : value(std::make_shared<T>(v)) {} std::unique_ptr<AnyHolder> clone() const override { return std::make_unique<Holder<T>>(*value); } std::shared_ptr<A> get_base() const override { return value; } std::type_index type() const override { return std::type_index(typeid(T)); } void* get_raw() const override { return value.get(); } }; struct Wrapper { std::vector<std::unique_ptr<AnyHolder>> data; template <typename DerivedType> void add(const DerivedType& e) { static_assert(std::is_base_of_v<A, DerivedType>, "类型必须派生自A"); data.push_back(std::make_unique<Holder<DerivedType>>(e)); } // 安全转换回原始类型,类型不匹配或索引越界返回空指针 template <typename T> std::shared_ptr<T> get(size_t index) { if (index >= data.size() || data[index]->type() != std::type_index(typeid(T))) { return nullptr; } return std::static_pointer_cast<T>(std::shared_ptr<T>(data[index]->get_raw())); } // 复制构造和赋值同方案1 Wrapper(const Wrapper& other) { for (const auto& holder : other.data) { data.push_back(holder->clone()); } } Wrapper& operator=(const Wrapper& other) { if (this != &other) { data.clear(); for (const auto& holder : other.data) { data.push_back(holder->clone()); } } return *this; } Wrapper(Wrapper&&) = default; Wrapper& operator=(Wrapper&&) = default; };
这两个方案都满足:无需显式指定模板参数、Wrapper可复制、能安全转换回原始派生类型。
内容的提问来源于stack exchange,提问作者François
相关产品推荐
相关产品推荐

