std::shared_ptr<Derived> 怎么可能在没有编译器错误的情况下转换为 std::shared_ptr<Base>?

How is it possible that std::shared_ptr<Derived> casts to std::shared_ptr<Base> with no compiler errors?

我正在尝试实现自定义智能指针。所以我有这样的东西:

// Base class for every object
class Base {
public:
    int n_holders {};
};

class Derived : public Base {};

// Custom shared pointer
template<class T>
class Sptr {
public:
    T* obj;
    Sptr(T* obj_) : obj{obj_} {}
};

void SomeFunc(Sptr<Base> obj) {}

void SomeFunc2(std::shared_ptr<Base> obj) {}

int main()
{
    auto a = Sptr<Base>(new Base());
    SomeFunc(a); // OK

    auto b = Sptr<Derived>(new Derived());
    SomeFunc(b); // Error. No matching function call to SomeFunc

    auto c = std::shared_ptr<Base>(new Base());
    SomeFunc2(c); // OK

    auto d = std::shared_ptr<Derived>(new Derived());
    SomeFunc2(d); // OK !!!
}

我的问题是,如果使用 std::shared_ptr 没有错误,为什么编译器不能自动从 Sptr<Derived> 转换为 Sptr<Base>?如何让它成为可能?

std::shared_ptr 有一个构造函数(来自 cppreference):

template< class Y >
shared_ptr( const shared_ptr<Y>& r ) noexcept;  (9)     

这个超载...

Constructs a shared_ptr which shares ownership of the object managed by r. If r manages no object, this manages no object too. The template overload doesn't participate in overload resolution if Y is not implicitly convertible to (until C++17)compatible with (since C++17) T*.

因此,从某种意义上说,棘手的部分不是转换共享指针,而是在指针类型不可隐式转换时防止它。您可以使用 SFINAE 来实现。

这是一个玩具示例,仅当 T1 继承自 T2 时才启用从 Bar<T1>Bar<T2> 的转换(但不是相反):

#include <type_traits>

template <typename T1>
struct Bar {
    Bar() {}

    template <typename T2, typename std::enable_if_t<std::is_base_of_v<T1,T2>,int> = 0>
    Bar(Bar<T2>){} 

};

struct Foo {};
struct Derived : Foo {};

int main(){
    Bar<Derived> d;
    Bar<Foo> b;
    //d = b; // eror
    b = d;  // OK
}

Live Demo

你可能希望它更通用,就像共享指针一样,只要 T2* 可以转换为 T1* 就允许这种转换,而不仅仅是当它们相互继承时(参见 std::is_convertible, I have to admit, I don't really understand the change that came with C++17, so I can only guess: maybe its std::is_layout_compatible 在这种情况下)。因此,要模仿 C++17 之前的智能指针,您可以使用:

    template <typename T2, typename std::enable_if_t<std::is_convertible_v<T2*,T1*>,int> = 0>
    Bar(Bar<T2>){} 

启用所有 T2 的转换,其中 T2* 可以转换为 T1*