[英]Overload method for unique_ptr and shared_ptr is ambiguous with polymorphism
Coding stuff after taking the hint from my previous question 's answer, I ran into an issue with overloading Scene::addObject. 在我从前一个问题的答案中提示之后编写了一些东西,我遇到了重载Scene :: addObject的问题。
To reiterate the relevant bits and make this self contained, with the least details possible: 重申相关位并使其自包含,尽可能少的细节:
Interface
of which there are Foo
s and Bar
s; Interface
的对象的层次结构,其中有Foo
和Bar
; Scene
which owns these objects; Scene
; Foo
s are to be unique_ptr
s and Bar
s are to be shared_ptr
s in my main (for reasons explained in the previous question); Foo
应该是unique_ptr
s,而Bar
应该是我主要的shared_ptr
(由于前一个问题中解释的原因); main
passes them to the Scene
instance, which takes ownership. main
将它们传递给Scene
实例,后者取得所有权。 Minimal code example is this : 最小的代码示例是这样的 :
#include <memory>
#include <utility>
class Interface
{
public:
virtual ~Interface() = 0;
};
inline Interface::~Interface() {}
class Foo : public Interface
{
};
class Bar : public Interface
{
};
class Scene
{
public:
void addObject(std::unique_ptr<Interface> obj);
// void addObject(std::shared_ptr<Interface> obj);
};
void Scene::addObject(std::unique_ptr<Interface> obj)
{
}
//void Scene::addObject(std::shared_ptr<Interface> obj)
//{
//}
int main(int argc, char** argv)
{
auto scn = std::make_unique<Scene>();
auto foo = std::make_unique<Foo>();
scn->addObject(std::move(foo));
// auto bar = std::make_shared<Bar>();
// scn->addObject(bar);
}
Uncommenting the commented lines results in: 取消注释注释行会导致:
error: call of overloaded 'addObject(std::remove_reference<std::unique_ptr<Foo, std::default_delete<Foo> >&>::type)' is ambiguous
scn->addObject(std::move(foo));
^
main.cpp:27:6: note: candidate: 'void Scene::addObject(std::unique_ptr<Interface>)'
void Scene::addObject(std::unique_ptr<Interface> obj)
^~~~~
main.cpp:31:6: note: candidate: 'void Scene::addObject(std::shared_ptr<Interface>)'
void Scene::addObject(std::shared_ptr<Interface> obj)
^~~~~
Uncommenting the shared and commenting the unique stuff also compiles, so I take it the problem is, like the compiler says, in the overload. 取消注释共享和注释的独特的东西也编译,所以我认为问题是,如编译器所说,在重载。 However I need the overload as both these types will need to be stored in some kind of collection, and they are indeed kept as pointers to base (possibly all moved into
shared_ptr
s). 但是我需要重载,因为这两种类型都需要存储在某种集合中,并且它们确实作为指向base的指针(可能全部移入
shared_ptr
)。
I'm passing both by-value because I want to make clear I'm taking ownership in Scene
(and upping the reference counter for the shared_ptr
s). 我正在传递两个值,因为我想说明我在
Scene
获取所有权(并且为shared_ptr
增加了参考计数器)。 Not really clear to me where the issue lies at all, and I couldn't find any example of this elsewhere. 对我来说不是很清楚问题在哪里,我在其他地方找不到任何这样的例子。
The problem you are encountering is this constructor of shared_ptr
(13) , (which is not explicit), is as good a match as a similar "moving derived to base" constructor of unique_ptr
(6) (also not explicit). 你遇到的问题是
shared_ptr
(13)的这个构造函数(它不是显式的),与unique_ptr
(6)的类似“ unique_ptr
derived to base”构造函数一样好(也不是显式的)。
template< class Y, class Deleter >
shared_ptr( std::unique_ptr<Y,Deleter>&& r ); // (13)
13) Constructs a
shared_ptr
which manages the object currently managed byr
.13)构造一个
shared_ptr
,它管理当前由r
管理的对象。 The deleter associated withr
is stored for future deletion of the managed object.存储与
r
相关联的删除器以供将来删除被管理对象。r
manages no object after the call.r
在通话后管理没有对象。This overload doesn't participate in overload resolution if
std::unique_ptr<Y, Deleter>::pointer
is not compatible withT*
.如果
std::unique_ptr<Y, Deleter>::pointer
与T*
不兼容,则此重载不参与重载解析。 Ifr.get()
is a null pointer, this overload is equivalent to the default constructor (1).如果
r.get()
是空指针,则此重载等效于默认构造函数(1)。 (since C++17)(自C ++ 17起)
template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept; //(6)
6) Constructs a
unique_ptr
by transferring ownership fromu
to*this
, whereu
is constructed with a specified deleter (E).6)通过将所有权从
u
转移到*this
来构造unique_ptr
,其中u
由指定的删除符(E)构成。This constructor only participates in overload resolution if all of the following is true:
如果满足以下所有条件,则此构造函数仅参与重载解析:
a)
unique_ptr<U, E>::pointer
is implicitly convertible to pointera)
unique_ptr<U, E>::pointer
可以隐式转换为指针b)
U
is not an array typeb)
U
不是数组类型c) Either
Deleter
is a reference type andE
is the same type asD
, orDeleter
is not a reference type andE
is implicitly convertible toD
c)
Deleter
是引用类型,E
是与D
相同的类型,或者Deleter
不是引用类型,E
可以隐式转换为D
In the non polymorphic case, you are constructing a unique_ptr<T>
from a unique_ptr<T>&&
, which uses the non-template move constructor. 在非多态性的情况下,你正在构建
unique_ptr<T>
从一个unique_ptr<T>&&
,它使用非模板移动的构造。 There overload resolution prefers the non-template 重载决策更喜欢非模板
I'm going to assume that Scene
stores shared_ptr<Interface>
s. 我将假设
Scene
存储shared_ptr<Interface>
。 In that case you don't need to overload addObject
for unique_ptr
, you can just allow the implicit conversion in the call. 在这种情况下,您不需要为
unique_ptr
重载addObject
,您可以在调用中允许隐式转换。
The other answer explains the ambiguity and a possible solution. 另一个答案解释了模糊性和可能的解决方案。 Here's another way in case you end up needing both overloads;
这是另一种方法,以防你最终需要两个重载; you can always add another parameter in such cases to break the ambiguity and use tag-dispatching.
你总是可以在这种情况下添加另一个参数来打破歧义并使用tag-dispatching。 The boiler-plate code is hidden in private part of
Scene
: 样板代码隐藏在
Scene
私有部分:
class Scene
{
struct unique_tag {};
struct shared_tag {};
template<typename T> struct tag_trait;
// Partial specializations are allowed in class scope!
template<typename T, typename D> struct tag_trait<std::unique_ptr<T,D>> { using tag = unique_tag; };
template<typename T> struct tag_trait<std::shared_ptr<T>> { using tag = shared_tag; };
void addObject_internal(std::unique_ptr<Interface> obj, unique_tag);
void addObject_internal(std::shared_ptr<Interface> obj, shared_tag);
public:
template<typename T>
void addObject(T&& obj)
{
addObject_internal(std::forward<T>(obj),
typename tag_trait<std::remove_reference_t<T>>::tag{});
}
};
You have declared two overloads, one taking std::unique_ptr<Interface>
and one taking std::shared_ptr<Interface>
but are passing in a parameter of type std::unique_ptr<Foo>
. 您已经声明了两个重载,一个采用
std::unique_ptr<Interface>
,另一个采用std::shared_ptr<Interface>
但是传入std::unique_ptr<Foo>
类型的参数。 As none of your functions match directly the compiler has to perform a conversion to call your function. 由于没有任何函数直接匹配,编译器必须执行转换才能调用函数。
There is one conversion available to std::unique_ptr<Interface>
(simple type conversion to unique pointer to base class) and another to std::shared_ptr<Interface>
(change to a shared pointer to the base class). 有一个转换可用于
std::unique_ptr<Interface>
(简单类型转换为指向基类的唯一指针),另一个转换为std::shared_ptr<Interface>
(更改为指向基类的共享指针)。 These conversions have the same priority so the compiler doesn't know which conversion to use so your functions are ambiguous. 这些转换具有相同的优先级,因此编译器不知道要使用哪个转换,因此您的函数不明确。
If you pass std::unique_ptr<Interface>
or std::shared_ptr<Interface>
there is no conversion required so there is no ambiguity. 如果传递
std::unique_ptr<Interface>
或std::shared_ptr<Interface>
,则不需要转换,因此不存在歧义。
The solution is to simply remove the unique_ptr
overload and always convert to shared_ptr
. 解决方案是简单地删除
unique_ptr
重载并始终转换为shared_ptr
。 This assumes that the two overloads have the same behaviour, if they don't renaming one of the methods could be more appropriate. 这假设两个重载具有相同的行为,如果它们不重命名其中一个方法可能更合适。
The solution by jrok is already quite good. jrok的解决方案已经非常好了。 The following approach allows to reuse code even better:
以下方法允许更好地重用代码:
#include <memory>
#include <utility>
#include <iostream>
#include <type_traits>
namespace internal {
template <typename S, typename T>
struct smart_ptr_rebind_trait {};
template <typename S, typename T, typename D>
struct smart_ptr_rebind_trait<S,std::unique_ptr<T,D>> { using rebind_t = std::unique_ptr<S>; };
template <typename S, typename T>
struct smart_ptr_rebind_trait<S,std::shared_ptr<T>> { using rebind_t = std::shared_ptr<S>; };
}
template <typename S, typename T>
using rebind_smart_ptr_t = typename internal::smart_ptr_rebind_trait<S,std::remove_reference_t<T>>::rebind_t;
class Interface
{
public:
virtual ~Interface() = 0;
};
inline Interface::~Interface() {}
class Foo : public Interface {};
class Bar : public Interface {};
class Scene
{
void addObject_internal(std::unique_ptr<Interface> obj) { std::cout << "unique\n"; }
void addObject_internal(std::shared_ptr<Interface> obj) { std::cout << "shared\n"; }
public:
template<typename T>
void addObject(T&& obj) {
using S = rebind_smart_ptr_t<Interface,T>;
addObject_internal( S(std::forward<T>(obj)) );
}
};
int main(int argc, char** argv)
{
auto scn = std::make_unique<Scene>();
auto foo = std::make_unique<Foo>();
scn->addObject(std::move(foo));
auto bar = std::make_shared<Bar>();
scn->addObject(bar); // ok
}
What we do here is to first introduce some helper classes that allow to rebind smart pointers. 我们在这里做的是首先介绍一些允许重新绑定智能指针的帮助程序类。
Foos are to be unique_ptrs and Bars are to be shared_ptrs in my main (for reasons explained in the previous question);
Foos应该是unique_ptrs,Bars应该是我主要的shared_ptrs(由于前一个问题中解释的原因);
Can you overload in terms of pointer-to- Foo
and pointer-to- Bar
instead of pointer-to- Interface
, since you want to treat them differently ? 你可以重写指针到
Foo
和指针到Bar
而不是指针到Interface
,因为你想要区别对待它们吗?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.