根据上一个问题的答案进行编码后,我遇到了重载Scene::addObject的问题。
重申相关的部分,并使其自我包含,尽可能少的细节:
- 我有一个从
Interface
继承的对象层次结构,其中有Foo
s和Bar
s - 我有一个
Scene
,它拥有这些对象 - 在我看来,
Foo
s将是unique_ptr
s,Bar
s将是shared_ptr
s(由于前面问题中解释的原因) main
将它们传递给取得所有权的Scene
实例
最小代码示例如下:
#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)的这个构造函数(不是显式的)与unique_ptr
(6)的类似"移动派生到基"构造函数(也不是显式)非常匹配。
template< class Y, class Deleter >
shared_ptr( std::unique_ptr<Y,Deleter>&& r ); // (13)
13)构造一个
shared_ptr
,该CCD_16管理当前由r
管理的对象。与CCD_ 18相关联的删除器被存储以供将来删除被管理对象。r
在调用之后不管理任何对象。如果
std::unique_ptr<Y, Deleter>::pointer
与T*
不兼容,则此过载不参与过载解决。如果r.get()
是空指针,则此重载等效于默认构造函数(1)。(自C++17起)
template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept; //(6)
6)通过将所有权从
u
转移到*this
来构造unique_ptr
,其中u
是用指定的deleter(E)构造的。只有当以下所有情况都成立时,此构造函数才会参与过载解决:
a)
unique_ptr<U, E>::pointer
可隐式转换为指针b)
U
不是阵列型c)
Deleter
是引用类型,E
与D
是同一类型,或者Deleter
不是引用类型,并且E
可以隐式转换为D
在非多态情况下,您将从unique_ptr<T>&&
构造unique_ptr<T>
,该CCD_36使用非模板移动构造函数。有过载分辨率偏好非模板
我假设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{});
}
};
完整的可编译示例在这里。
您已经声明了两个重载,一个采用std::unique_ptr<Interface>
,一个使用std::shared_ptr<Interface>
,但正在传递类型为std::unique_ptr<Foo>
的参数。由于没有一个函数直接匹配,编译器必须执行转换才能调用函数。
有一种转换可用于std::unique_ptr<Interface>
(到基类的唯一指针的简单类型转换),另一种转换用于std::shared_ptr<Interface>
(更改为到基类的共享指针)。这些转换具有相同的优先级,因此编译器不知道使用哪种转换,因此您的函数不明确。
如果您通过std::unique_ptr<Interface>
或std::shared_ptr<Interface>
,则不需要转换,因此不存在歧义。
解决方案是简单地去除unique_ptr
过载并始终转换为shared_ptr
。这假设两个重载具有相同的行为,如果它们不重命名其中一个方法可能更合适。
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 << "uniquen"; }
void addObject_internal(std::shared_ptr<Interface> obj) { std::cout << "sharedn"; }
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
}
我们在这里要做的是首先引入一些帮助类,它们允许重新绑定智能指针。
Foos将是unique_ptrs,Bars将是shared_ptrs(原因在上一个问题中解释);
您是否可以根据指向-Foo
的指针和指向-CCD五十二的指针而不是指向-CCD五十三的指针进行重载,因为您希望区别对待它们?