`shared_ptr` 如何实现协方差?

How do `shared_ptr`s achieve covariance?

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

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

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

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

是的,默认情况下,同一 class 模板的特化几乎没有任何关系,基本上被视为不相关的类型。但是您始终可以通过定义转换构造函数 (To::To(const From&)) and/or 转换函数 (From::operator To() const).

来定义 class 类型之间的隐式转换

那么 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 and [util.smartptr.const]/18 and util.smartptr.const]/21):

For the purposes of subclause [util.smartptr], a pointer type Y* is said to be compatible with a pointer type T* when either Y* is convertible to T* or Y is U[N] and T is cv U[].

The [...] constructor shall not participate in overload resolution unless Y* is compatible with 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,或者 false 否则。那么 std::enable_if 是一个特征,当它的第一个模板参数是 true 时,它有一个名为 type 的成员类型,或者当它的第一个模板参数是 type 时,没有一个名为 type 的成员false,使类型别名 std::enable_if_t 无效。

因此,如果任一构造函数的模板类型推导推导出类型 Y,使得 Y*T* 不兼容,则将 Y 替换为 [=41] =] 默认模板参数无效。由于在替换推导的模板参数时会发生这种情况,因此效果只是将整个函数模板从重载决议的考虑中移除。有时 SFINAE 技术被用来强制选择不同的重载,或者像这里一样(大多数情况下),它只会使用户的代码无法编译。尽管在编译错误的情况下,它会帮助在输出中某处显示模板无效的消息,而不是内部模板代码中更深层次的错误。 (此外,像这样的 SFINAE 设置使得不同的模板可以使用自己的 SFINAE 技术来测试某个模板特化、类型相关表达式等是否有效。)