`shared_ptr` 如何实现协方差?
How do `shared_ptr`s achieve covariance?
可以从 shared_ptr<Deriver>
复制或构建 shared_ptr<Base>
(即 shared_ptr<Base> ptr = make_shared<Derived>()
)。但众所周知,模板 类 不能相互转换,即使模板参数可以。那么 shared_ptr
s 如何检查它们的指针的值是否可转换,如果是则进行转换?
之所以有效,是因为 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 技术来测试某个模板特化、类型相关表达式等是否有效。)
可以从 shared_ptr<Deriver>
复制或构建 shared_ptr<Base>
(即 shared_ptr<Base> ptr = make_shared<Derived>()
)。但众所周知,模板 类 不能相互转换,即使模板参数可以。那么 shared_ptr
s 如何检查它们的指针的值是否可转换,如果是则进行转换?
之所以有效,是因为 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
).
那么 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 typeT*
when eitherY*
is convertible toT*
orY
isU[N]
andT
is cvU[]
.The [...] constructor shall not participate in overload resolution unless
Y*
is compatible withT*
.
尽管可以通过任何方式(包括特定于编译器的功能)执行此限制,但大多数实现将使用 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 技术来测试某个模板特化、类型相关表达式等是否有效。)