shared_ptr的如何实现协方差?

时间:2019-03-09 23:16:12

标签: c++ inheritance polymorphism shared-ptr covariance

可以从shared_ptr<Base>(即shared_ptr<Deriver>)复制或构造shared_ptr<Base> ptr = make_shared<Derived>()。但是众所周知,即使模板参数是可转换的,模板类也不能相互转换。那么shared_ptr的指针如何检查其指针的值是否可转换,如果指针可以转换,则如何进行转换?

2 个答案:

答案 0 :(得分:4)

是的,默认情况下,同一类模板的专业化几乎没有关系,并且本质上被视为不相关的类型。但是,您始终可以通过定义转换构造函数(To::To(const From&)和/或转换函数(From::operator To() const)来定义类类型之间的隐式转换。

所以std::shared_ptr所做的是定义模板转换构造函数:

namespace std {
    template <class T>
    class shared_ptr {
    public:
        template <class Y>
        shared_ptr(const shared_ptr<Y>&);
        template <class Y>
        shared_ptr(shared_ptr<Y>&&);
        // ...
    };
}

尽管所示的声明将允许从任何shared_ptr到任何其他的转换,而不仅仅是在模板参数类型兼容的情况下。但是标准还提到了这些构造函数([util.smartptr]/5[util.smartptr.const]/18util.smartptr.const]/21):

  

出于第[util.smartptr]节的目的,当任一Y*时,指针类型T*与指针类型Y*兼容 可以转换为T*YU[N]T cv U[]

     

除非Y*T*兼容,否则构造函数不得参与重载解析。

尽管可以以任何方式完成此限制,包括特定于编译器的功能,但大多数实现将使用SFINAE技术(“替代失败不是错误”)强制执行该限制。一种可能的实现方式:

#include <cstddef>
#include <type_traits>

namespace std {
    template <class Y, class T>
    struct __smartptr_compatible
        : is_convertible<Y*, T*> {};

    template <class U, class V, size_t N>
    struct __smartptr_compatible<U[N], V[]>
        : bool_constant<is_same_v<remove_cv_t<U>, remove_cv_t<V>> &&
                        is_convertible_v<U*, V*>> {};

    template <class T>
    class shared_ptr {
    public:
        template <class Y, class = enable_if_t<__smartptr_compatible<Y, T>::value>>
        shared_ptr(const shared_ptr<Y>&);

        template <class Y, class = enable_if_t<__smartptr_compatible<Y, T>::value>>
        shared_ptr(shared_ptr<Y>&&);

        // ...
    };
}

这里,辅助模板__smartptr_compatible<Y, T>充当“特征”:它有一个static constexpr成员value,当类型与定义兼容时,该成员就是true或{ {1}}否则。那么false是一个特征,当其第一个模板参数为std::enable_if时,其成员类型为type,而当其第一个模板参数为true时,该成员的名称为type false,使类型别名std::enable_if_t无效。

因此,如果任一构造函数的模板类型推导都推导了类型Y,从而Y*T*不兼容,则将Y替换为enable_if_t默认模板参数无效。由于这是在替换推导的模板参数时发生的,因此效果只是从考虑重载解析的角度删除了整个函数模板。有时,有时会使用SFINAE技术来强制选择其他重载,或者如此处(大部分时间)那样,它可能会使用户的代码无法编译。虽然在发生编译错误的情况下,这将有助于在输出中某处显示模板无效的消息,而不是内部模板代码中更深的错误。 (此外,这样的SFINAE设置可以使其他模板使用其自己的SFINAE技术来测试某个模板的专业化,与类型相关的表达式等是否有效。)

答案 1 :(得分:-1)

它之所以有效,是因为shared_ptr(以及其他)具有模板化的构造函数

template<typename U> shared_ptr(U * ptr);

如果U *不能转换为shared_ptr的包含类型,那么您将在shared_ptr实现中的某个地方发现错误。