unique_ptr和shared_ptr的重载方法与多态性不一致

Lab*_*ica 25 c++ overloading shared-ptr unique-ptr c++14

在我从前一个问题的答案中提示之后编写了一些东西,我遇到了重载Scene :: addObject的问题.

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

  • 我有继承对象的层次Interface,其中有FooS和BarS;
  • 我有一个Scene拥有这些物品的人;
  • Foos应该是unique_ptrs和Bars将shared_ptr在我的主要(由于前一个问题中解释的原因);
  • 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);
}
Run Code Online (Sandbox Code Playgroud)

取消注释注释行会导致:

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)

      ^~~~~
Run Code Online (Sandbox Code Playgroud)

取消注释共享和注释的独特的东西也编译,所以我认为问题是,如编译器所说,在重载.但是我需要重载,因为这两种类型都需要存储在某种集合中,并且它们确实被保存为指向base的指针(可能全部移入shared_ptrs).

我正在传递两个值,因为我想说清楚我正在取得所有权Scene(并为shared_ptrs 增加参考计数器).对我来说不是很清楚问题在哪里,我在其他地方找不到任何这样的例子.

Cal*_*eth 18

你遇到的问题是shared_ptr(13)的这个构造函数(它不是显式的),与unique_ptr(6)的类似"移动派生到基础"构造函数(也不是显式)一样好.

template< class Y, class Deleter > 
shared_ptr( std::unique_ptr<Y,Deleter>&& r ); // (13)
Run Code Online (Sandbox Code Playgroud)

13)构造一个shared_ptr管理当前管理的对象的r.r存储与之关联的删除器以供将来删除受管对象.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)
Run Code Online (Sandbox Code Playgroud)

6)unique_ptr通过转移所有权u来构造a *this,其中u使用指定的删除符(E)构造.

如果满足以下所有条件,则此构造函数仅参与重载解析:

a)unique_ptr<U, E>::pointer可隐式转换为指针

b)U不是数组类型

c)要么Deleter是引用类型,要么是E类型D,要么Deleter不是引用类型,并且E可以隐式转换为D

在非多态的情况下,您正在构造一个unique_ptr<T>from unique_ptr<T>&&,它使用非模板移动构造函数.重载决策更喜欢非模板


我打算假设Scene商店shared_ptr<Interface>.在这种情况下,你不需要超载addObjectunique_ptr,你可以只允许在呼叫中的隐式转换.


jro*_*rok 5

另一个答案解释了歧义和可能的解决方案。这是另一种方法,以防您最终需要两个重载;在这种情况下,您始终可以添加另一个参数以消除歧义并使用标签调度。样板代码隐藏在以下的私有部分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{});
    }
};
Run Code Online (Sandbox Code Playgroud)

完整的可编译示例在这里。