unique_ptr 和 shared_ptr 的重载方法与多态性不明确

Overload method for unique_ptr and shared_ptr is ambiguous with polymorphism

在从我的 的回答中得到提示后编写代码,我 运行 遇到了重载 Scene::addObject.

的问题

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

最小代码示例是 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_ptrs)。

我按值传递这两个值,因为我想表明我正在取得 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
}

我们在这里做的是首先介绍一些允许重新绑定智能指针的助手类。