unique_ptr和shared_ptr的重载方法由于多态性而不明确



根据上一个问题的答案进行编码后,我遇到了重载Scene::addObject的问题。

重申相关的部分,并使其自我包含,尽可能少的细节:

  • 我有一个从Interface继承的对象层次结构,其中有Foos和Bars
  • 我有一个Scene,它拥有这些对象
  • 在我看来,Foos将是unique_ptrs,Bars将是shared_ptrs(由于前面问题中解释的原因)
  • 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_ptrs中)。

我通过值传递这两个值,因为我想明确我正在获得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>::pointerT*不兼容,则此过载不参与过载解决。如果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是引用类型,ED是同一类型,或者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五十三的指针进行重载,因为您希望区别对待它们?

相关内容

  • 没有找到相关文章

最新更新