简体   繁体   English

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

[英]Overload method for unique_ptr and shared_ptr is ambiguous with polymorphism

Coding stuff after taking the hint from my previous question 's answer, I ran into an issue with overloading Scene::addObject. 在我从前一个问题的答案中提示之后编写了一些东西,我遇到了重载Scene :: addObject的问题。

To reiterate the relevant bits and make this self contained, with the least details possible: 重申相关位并使其自包含,尽可能少的细节:

  • I have a hierarchy of objects inheriting from Interface of which there are Foo s and Bar s; 我有一个继承自Interface的对象的层次结构,其中有FooBar ;
  • I have a Scene which owns these objects; 我有一个拥有这些物品的Scene ;
  • Foo s are to be unique_ptr s and Bar s are to be shared_ptr s in my main (for reasons explained in the previous question); Foo应该是unique_ptr s,而Bar应该是我主要的shared_ptr (由于前一个问题中解释的原因);
  • the main passes them to the Scene instance, which takes ownership. main将它们传递给Scene实例,后者取得所有权。

Minimal code example is 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);
}

Uncommenting the commented lines results in: 取消注释注释行会导致:

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)

      ^~~~~

Uncommenting the shared and commenting the unique stuff also compiles, so I take it the problem is, like the compiler says, in the overload. 取消注释共享和注释的独特的东西也编译,所以我认为问题是,如编译器所说,在重载。 However I need the overload as both these types will need to be stored in some kind of collection, and they are indeed kept as pointers to base (possibly all moved into shared_ptr s). 但是我需要重载,因为这两种类型都需要存储在某种集合中,并且它们确实作为指向base的指针(可能全部移入shared_ptr )。

I'm passing both by-value because I want to make clear I'm taking ownership in Scene (and upping the reference counter for the shared_ptr s). 我正在传递两个值,因为我想说明我在Scene获取所有权(并且为shared_ptr增加了参考计数器)。 Not really clear to me where the issue lies at all, and I couldn't find any example of this elsewhere. 对我来说不是很清楚问题在哪里,我在其他地方找不到任何这样的例子。

The problem you are encountering is this constructor of shared_ptr (13) , (which is not explicit), is as good a match as a similar "moving derived to base" constructor of unique_ptr (6) (also not explicit). 你遇到的问题是shared_ptr (13)的这个构造函数(它不是显式的),与unique_ptr (6)的类似“ unique_ptr derived to base”构造函数一样好(也不是显式的)。

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 . 13)构造一个shared_ptr ,它管理当前由r管理的对象。 The deleter associated with r is stored for future deletion of the managed object. 存储与r相关联的删除器以供将来删除被管理对象。 r manages no object after the call. r在通话后管理没有对象。

This overload doesn't participate in overload resolution if std::unique_ptr<Y, Deleter>::pointer is not compatible with T* . 如果std::unique_ptr<Y, Deleter>::pointerT*不兼容,则此重载不参与重载解析。 If r.get() is a null pointer, this overload is equivalent to the default constructor (1). 如果r.get()是空指针,则此重载等效于默认构造函数(1)。 (since C++17) (自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). 6)通过将所有权从u转移到*this来构造unique_ptr ,其中u由指定的删除符(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 a) unique_ptr<U, E>::pointer可以隐式转换为指针

b) U is not an array type b) U不是数组类型

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 c) Deleter是引用类型, E是与D相同的类型,或者Deleter不是引用类型, E可以隐式转换为D

In the non polymorphic case, you are constructing a unique_ptr<T> from a unique_ptr<T>&& , which uses the non-template move constructor. 在非多态性的情况下,你正在构建unique_ptr<T>从一个unique_ptr<T>&& ,它使用非模板移动的构造。 There overload resolution prefers the non-template 重载决策更喜欢非模板


I'm going to assume that Scene stores shared_ptr<Interface> s. 我将假设Scene存储shared_ptr<Interface> In that case you don't need to overload addObject for unique_ptr , you can just allow the implicit conversion in the call. 在这种情况下,您不需要为unique_ptr重载addObject ,您可以在调用中允许隐式转换。

The other answer explains the ambiguity and a possible solution. 另一个答案解释了模糊性和可能的​​解决方案。 Here's another way in case you end up needing both overloads; 这是另一种方法,以防你最终需要两个重载; you can always add another parameter in such cases to break the ambiguity and use tag-dispatching. 你总是可以在这种情况下添加另一个参数来打破歧义并使用tag-dispatching。 The boiler-plate code is hidden in private part of Scene : 样板代码隐藏在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{});
    }
};

Full compilable example is here. 这里有完整的可编辑示例

You have declared two overloads, one taking std::unique_ptr<Interface> and one taking std::shared_ptr<Interface> but are passing in a parameter of type std::unique_ptr<Foo> . 您已经声明了两个重载,一个采用std::unique_ptr<Interface> ,另一个采用std::shared_ptr<Interface>但是传入std::unique_ptr<Foo>类型的参数。 As none of your functions match directly the compiler has to perform a conversion to call your function. 由于没有任何函数直接匹配,编译器必须执行转换才能调用函数。

There is one conversion available to std::unique_ptr<Interface> (simple type conversion to unique pointer to base class) and another to std::shared_ptr<Interface> (change to a shared pointer to the base class). 有一个转换可用于std::unique_ptr<Interface> (简单类型转换为指向基类的唯一指针),另一个转换为std::shared_ptr<Interface> (更改为指向基类的共享指针)。 These conversions have the same priority so the compiler doesn't know which conversion to use so your functions are ambiguous. 这些转换具有相同的优先级,因此编译器不知道要使用哪个转换,因此您的函数不明确。

If you pass std::unique_ptr<Interface> or std::shared_ptr<Interface> there is no conversion required so there is no ambiguity. 如果传递std::unique_ptr<Interface>std::shared_ptr<Interface> ,则不需要转换,因此不存在歧义。

The solution is to simply remove the unique_ptr overload and always convert to shared_ptr . 解决方案是简单地删除unique_ptr重载并始终转换为shared_ptr This assumes that the two overloads have the same behaviour, if they don't renaming one of the methods could be more appropriate. 这假设两个重载具有相同的行为,如果它们不重命名其中一个方法可能更合适。

The solution by jrok is already quite good. jrok的解决方案已经非常好了。 The following approach allows to reuse code even better: 以下方法允许更好地重用代码:

#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
}

What we do here is to first introduce some helper classes that allow to rebind smart pointers. 我们在这里做的是首先介绍一些允许重新绑定智能指针的帮助程序类。

Foos are to be unique_ptrs and Bars are to be shared_ptrs in my main (for reasons explained in the previous question); Foos应该是unique_ptrs,Bars应该是我主要的shared_ptrs(由于前一个问题中解释的原因);

Can you overload in terms of pointer-to- Foo and pointer-to- Bar instead of pointer-to- Interface , since you want to treat them differently ? 你可以重写指针到Foo和指针到Bar而不是指针到Interface ,因为你想要区别对待它们吗?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM