std::shared_ptr<Derived> 如何在没有编译器错误的情况下转换为 std::shared_ptr<Base> ?

时间:2021-02-10 12:39:48

标签: c++ templates casting derived-class

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

// 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 !!!
}

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

1 个答案:

答案 0 :(得分:3)

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

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

这个重载...

<块引用>

构造一个共享管理对象所有权的 shared_ptr 由河如果 r 不管理任何对象,this 也不管理任何对象。这 如果 Y 是,模板重载不参与重载决议 不能隐式转换为(直到 C++17)兼容(因为 C++17) T*。

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

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

#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*,而不仅仅是在它们相互继承时(参见 {{3} },我不得不承认,我并不真正理解 C++17 带来的变化,所以我只能猜测:在这种情况下可能是 std::is_convertible)。因此,要模仿 C++17 之前的智能指针,您可以使用:

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

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