为何std::shared_ptr<Derived>可无错转为std::shared_ptr<Base> 自定义智能指针如何实现该转换
复现场景
给定如下测试代码:
// 所有对象继承的基类,持有引用计数 class Base { public: int n_holders {}; }; class Derived : public Base {}; // 极简实现的自定义智能指针 template<class T> class Sptr { public: T* obj; Sptr(T* obj_) : obj{obj_} {} }; void SomeFunc(Sptr<Base> obj) {} void SomeFunc2(std::shared_ptr<Base> obj) {} int main() { auto a = Sptr<Base>(new Base()); SomeFunc(a); // 编译通过 auto b = Sptr<Derived>(new Derived()); SomeFunc(b); // 编译报错:无匹配的函数重载 auto c = std::shared_ptr<Base>(new Base()); SomeFunc2(c); // 编译通过 auto d = std::shared_ptr<Derived>(new Derived()); SomeFunc2(d); // 编译通过 }
问题根因
- 模板类的不同实例是完全独立的类型:
Sptr<Derived>和Sptr<Base>在编译器看来没有任何继承或关联关系,不存在天生的类型转换通道 - 你当前实现的
Sptr仅提供了接收原生指针T*的构造函数,没有任何可以接收其他Sptr<U>类型参数的构造逻辑,编译器找不到可以触发隐式转换的入口,自然会报无匹配函数的错误 std::shared_ptr之所以可以实现跨模板参数的转换,本质是内部实现了专门的模板化转换构造函数,只要满足类型间的指针可转换规则,就能自动完成类型适配
修正方案
要让自定义Sptr达到和std::shared_ptr一致的转换效果,只需要在类中新增带类型约束的模板转换构造函数即可,修改后的实现如下:
template<class T> class Sptr { // 允许其他模板实例访问内部私有成员,按需添加 template<class> friend class Sptr; public: T* obj; // 保留原有原生指针构造逻辑 Sptr(T* obj_) : obj{obj_} {} // 核心新增:跨类型转换构造函数 template<class U> Sptr(const Sptr<U>& other) // 增加编译期校验:仅当U*可以隐式转换为T*时,该构造函数才生效 requires std::is_convertible_v<U*, T*> : obj(other.obj) {} };
实现说明
- 新增的转换构造函数会在
Sptr<Derived>传入需要Sptr<Base>的场景时,被编译器自动匹配:因为Derived*本身可以隐式转换为Base*,满足类型约束要求,编译器会自动构造一个临时的Sptr<Base>对象完成传参,和std::shared_ptr的行为完全一致 - 类型约束
std::is_convertible_v<U*, T*>会拦截非法转换,比如尝试把Sptr<Base>转成Sptr<Derived>时,由于Base*无法隐式转成Derived*,该构造函数不会被匹配,直接触发编译报错,符合原生指针的转换规则 - 如果后续给
Sptr补充引用计数逻辑,只需要在这个转换构造函数中复用普通拷贝构造的计数更新逻辑即可,不需要额外修改接口
内容的提问来源于stack exchange,提问作者Евгений Павлов
相关产品推荐
相关产品推荐

