`shared_ptr`s 如何实现协方差?

Gam*_*anA 5 c++ polymorphism inheritance covariance shared-ptr

可以shared_ptr<Base>shared_ptr<Deriver>(即shared_ptr<Base> ptr = make_shared<Derived>())复制或构造一个。但是我们都知道,模板类之间是不能相互转换的,即使模板参数可以。那么如何shared_ptr检查它们的指针的值是否可以转换,如果可以转换呢?

asc*_*ler 9

是的,默认情况下,同一类模板的特化几乎没有关系,并且本质上被视为不相关的类型。但是您始终可以通过定义转换构造函数 ( 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>&&);
        // ...
    };
}
Run Code Online (Sandbox Code Playgroud)

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

出于子条款 [util.smartptr] 的目的,当指针类型可转换为或is且is cv时,指针类型Y*被称为与指针类型兼容T*Y*T*YU[N]T 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>&&);

        // ...
    };
}
Run Code Online (Sandbox Code Playgroud)

这里,帮助器模板__smartptr_compatible<Y, T>充当“特征”:它有一个static constexpr成员valuetrue当类型按照定义兼容时,或者false以其他方式兼容。thenstd::enable_if是一个特征,type当它的第一个模板参数是 时,它有一个被调用的成员类型,或者当它的第一个模板参数是 时,true它没有一个被命名的成员,使得类型别名无效。typefalsestd::enable_if_t

因此,如果任一构造函数的模板类型推导推导出与 不兼容的类型YY*则将T*其替换Yenable_if_t默认模板参数是无效的。由于这种情况是在替换推导的模板参数时发生的,因此效果只是从重载解析的考虑中删除整个函数模板。有时,SFINAE 技术用于强制选择不同的重载,或者像这里一样(大多数时候),它只会使用户的代码无法编译。尽管在编译错误的情况下,在输出中的某处出现一条消息表明模板无效,而不是在内部模板代码中出现更深层的错误,这将有所帮助。(此外,这样的 SFINAE 设置使得不同的模板可以使用自己的 SFINAE 技术来测试某个模板专门化、依赖于类型的表达式等是否有效。)