【问题标题】:How do `shared_ptr`s achieve covariance?`shared_ptr`s 如何实现协方差?
【发布时间】:2019-03-09 23:16:11
【问题描述】:

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

【问题讨论】:

  • 如果您在谈论shared_ptr 的代码,那么 1- 因为我不知道如何找到它。 2-即使我确实找到了,阅读标准库代码也不是学习新编程技术的最佳方式。特别是对于像我这样的新人:)
  • 这就是 Template Magic Goo 的黑色艺术。如果您是新手,可能有点难以理解。如果你下定决心,最好的资源是尽可能多地学习 C++ 模板、泛型编程和元编程,并阅读 shared_ptr 和 make_shared 代码(因为模板代码就在其头文件中)。
  • 我怀疑如果您只是编写代码以将内部指针存放在不同类型的模板类中,编译器会告诉您您是否犯了错误。如果内部指针兼容,那么编译器就可以了。
  • @Neil Butterworth。不是真的,我主要关心 shared_ptr 类如何相互转换。我使用make_shared 的唯一原因是我从用于测试此功能的代码中复制并粘贴了它。如果这会误导任何人关于问题的性质,我深表歉意
  • @ChrisDodd 我会说这更多是对虚函数返回类型的协方差的限制,而不是shared_ptrshared_ptr<Derived> 可以“用作”shared_ptr<Base> 这一事实是一种协方差。

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


【解决方案1】:

是的,默认情况下,同一个类模板的特化几乎没有关系,本质上被视为不相关的类型。但是您始终可以通过定义转换构造函数 (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]Tcv 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&lt;Y, T&gt; 充当“特征”:它有一个static constexpr 成员value,当类型按照定义兼容时为true,否则为false。那么std::enable_if 是一个特征,当它的第一个模板参数是true 时有一个名为type 的成员类型,或者当它的第一个模板参数是false 时没有一个名为type 的成员,使类型别名std::enable_if_t 无效。

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

【讨论】:

  • 而有N个参数的特化有什么意义呢?是用于unique_ptr持有管理动态数组的时候吗?
  • 即匹配“兼容”的指定定义。它允许从具有特定大小的数组的指针到具有未知边界的数组的指针的转换,例如,从std::shared_ptr&lt;int[8]&gt;std::shared_ptr&lt;const int[]&gt;,即使您不能将int (*)[8] 隐式转换为const int (*)[]
【解决方案2】:

之所以有效,是因为 shared_ptr 有(除其他外)一个模板化的构造函数

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

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

【讨论】:

  • 不完全是,我们询问将共享 ptr 传递给派生的 OP。并且“参与重载决议”的措辞是 99% 的机会在这里使用。
  • 关闭,但make_shared 返回的shared_ptrU* 不匹配。它匹配不同的构造函数模板shared_ptr(shared_ptr&lt;U&gt;&amp;&amp;)。此外,“埋在实现中的某个地方”的错误不太可能:构造函数模板不能通过重载决议选择,因此错误跟踪应该只深入到构造函数的实际定义中。
猜你喜欢
  • 2013-11-11
  • 2011-02-10
  • 1970-01-01
  • 2011-06-18
  • 1970-01-01
  • 2011-02-09
  • 2020-11-19
  • 1970-01-01
相关资源
最近更新 更多