unique_ptr 和 shared_ptr 的重载方法与多态性不明确
Overload method for unique_ptr and shared_ptr is ambiguous with polymorphism
在从我的 的回答中得到提示后编写代码,我 运行 遇到了重载 Scene::addObject.
的问题
重申相关位并使其自包含,尽可能少的细节:
- 我有一个继承自
Interface
的对象层次结构,其中有 Foo
和 Bar
;
- 我有一个
Scene
拥有这些对象;
Foo
s 将成为 unique_ptr
s 并且 Bar
s 将成为 shared_ptr
s 在我的主要(出于上一个问题中解释的原因);
main
将它们传递给 Scene
实例,该实例获得所有权。
最小代码示例是 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);
}
取消对注释行的注释会导致:
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)
^~~~~
取消对共享内容的注释和对唯一内容的注释也会编译,所以我认为问题出在,就像编译器所说的那样,在重载中。但是我需要重载,因为这两种类型都需要存储在某种集合中,并且它们确实作为指向基的指针保存(可能全部移入 shared_ptr
s)。
我按值传递这两个值,因为我想表明我正在取得 Scene
的所有权(并增加 shared_ptr
的引用计数器)。我不太清楚问题出在哪里,我在其他地方也找不到这方面的例子。
您遇到的问题是 shared_ptr
(13), (which is not explicit), is as good a match as a similar "moving derived to base" constructor of unique_ptr
(6) 的这个构造函数(也不是显式的)。
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 by r
. The deleter associated with r
is stored for future deletion of the managed object. r
manages no object after the call.
This overload doesn't participate in overload resolution if std::unique_ptr<Y, Deleter>::pointer
is not compatible with T*
. If r.get()
is a null pointer, this overload is equivalent to the default constructor (1). (since C++17)
template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept; //(6)
6) Constructs a unique_ptr
by transferring ownership from u
to *this
, where u
is constructed with a specified deleter (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 pointer
b) U
is not an array type
c) Either Deleter
is a reference type and E
is the same type as D
, or Deleter
is not a reference type and E
is implicitly convertible to D
在非多态情况下,您从 unique_ptr<T>&&
构造 unique_ptr<T>
,它使用非模板移动构造函数。重载决议更喜欢非模板
我假设 Scene
存储 shared_ptr<Interface>
s。在那种情况下,您不需要为 unique_ptr
重载 addObject
,您可以只允许在调用中进行隐式转换。
另一个答案解释了歧义和可能的解决方案。如果您最终需要两个重载,这是另一种方法;在这种情况下,您总是可以添加另一个参数来打破歧义并使用标签调度。样板代码隐藏在 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{});
}
};
完整的可编译示例是 here.
您已经声明了两个重载,一个采用 std::unique_ptr<Interface>
,一个采用 std::shared_ptr<Interface>
,但正在传递 std::unique_ptr<Foo>
类型的参数。由于 none 您的函数直接匹配,编译器必须执行转换才能调用您的函数。
有一种转换可用于 std::unique_ptr<Interface>
(简单类型转换为指向基 class 的唯一指针)和另一种用于 std::shared_ptr<Interface>
(更改为指向基 [=] 的共享指针27=]).这些转换具有相同的优先级,因此编译器不知道要使用哪个转换,因此您的函数不明确。
如果您传递 std::unique_ptr<Interface>
或 std::shared_ptr<Interface>
,则不需要转换,因此不会产生歧义。
解决方案是简单地删除 unique_ptr
重载并始终转换为 shared_ptr
。这假定两个重载具有相同的行为,如果它们不重命名其中一种方法可能更合适。
Foos are to be unique_ptrs and Bars are to be shared_ptrs in my main (for reasons explained in the previous question);
是否可以重载指向 Foo
的指针和指向 Bar
的指针而不是指向 Interface
的指针,因为您想区别对待它们?
jrok的解决方案已经很不错了。以下方法允许更好地重用代码:
#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
}
我们在这里做的是首先介绍一些允许重新绑定智能指针的助手类。
在从我的
重申相关位并使其自包含,尽可能少的细节:
- 我有一个继承自
Interface
的对象层次结构,其中有Foo
和Bar
; - 我有一个
Scene
拥有这些对象; Foo
s 将成为unique_ptr
s 并且Bar
s 将成为shared_ptr
s 在我的主要(出于上一个问题中解释的原因);main
将它们传递给Scene
实例,该实例获得所有权。
最小代码示例是 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);
}
取消对注释行的注释会导致:
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)
^~~~~
取消对共享内容的注释和对唯一内容的注释也会编译,所以我认为问题出在,就像编译器所说的那样,在重载中。但是我需要重载,因为这两种类型都需要存储在某种集合中,并且它们确实作为指向基的指针保存(可能全部移入 shared_ptr
s)。
我按值传递这两个值,因为我想表明我正在取得 Scene
的所有权(并增加 shared_ptr
的引用计数器)。我不太清楚问题出在哪里,我在其他地方也找不到这方面的例子。
您遇到的问题是 shared_ptr
(13), (which is not explicit), is as good a match as a similar "moving derived to base" constructor of unique_ptr
(6) 的这个构造函数(也不是显式的)。
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
. The deleter associated withr
is stored for future deletion of the managed object.r
manages no object after the call.This overload doesn't participate in overload resolution if
std::unique_ptr<Y, Deleter>::pointer
is not compatible withT*
. Ifr.get()
is a null pointer, this overload is equivalent to the default constructor (1). (since 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).This constructor only participates in overload resolution if all of the following is true:
a)
unique_ptr<U, E>::pointer
is implicitly convertible to pointerb)
U
is not an array typec) Either
Deleter
is a reference type andE
is the same type asD
, orDeleter
is not a reference type andE
is implicitly convertible toD
在非多态情况下,您从 unique_ptr<T>&&
构造 unique_ptr<T>
,它使用非模板移动构造函数。重载决议更喜欢非模板
我假设 Scene
存储 shared_ptr<Interface>
s。在那种情况下,您不需要为 unique_ptr
重载 addObject
,您可以只允许在调用中进行隐式转换。
另一个答案解释了歧义和可能的解决方案。如果您最终需要两个重载,这是另一种方法;在这种情况下,您总是可以添加另一个参数来打破歧义并使用标签调度。样板代码隐藏在 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{});
}
};
完整的可编译示例是 here.
您已经声明了两个重载,一个采用 std::unique_ptr<Interface>
,一个采用 std::shared_ptr<Interface>
,但正在传递 std::unique_ptr<Foo>
类型的参数。由于 none 您的函数直接匹配,编译器必须执行转换才能调用您的函数。
有一种转换可用于 std::unique_ptr<Interface>
(简单类型转换为指向基 class 的唯一指针)和另一种用于 std::shared_ptr<Interface>
(更改为指向基 [=] 的共享指针27=]).这些转换具有相同的优先级,因此编译器不知道要使用哪个转换,因此您的函数不明确。
如果您传递 std::unique_ptr<Interface>
或 std::shared_ptr<Interface>
,则不需要转换,因此不会产生歧义。
解决方案是简单地删除 unique_ptr
重载并始终转换为 shared_ptr
。这假定两个重载具有相同的行为,如果它们不重命名其中一种方法可能更合适。
Foos are to be unique_ptrs and Bars are to be shared_ptrs in my main (for reasons explained in the previous question);
是否可以重载指向 Foo
的指针和指向 Bar
的指针而不是指向 Interface
的指针,因为您想区别对待它们?
jrok的解决方案已经很不错了。以下方法允许更好地重用代码:
#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
}
我们在这里做的是首先介绍一些允许重新绑定智能指针的助手类。