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
}
你可能希望它更通用,就像共享指针一样,只要 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*
。
我正在尝试实现自定义智能指针。所以我有这样的东西:
// 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
}
你可能希望它更通用,就像共享指针一样,只要 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*
。