Метод перегрузки для unique_ptr и shared_ptr неоднозначен с полиморфизмом - PullRequest
0 голосов
/ 26 ноября 2018

При написании кода после подсказки из моего предыдущего вопроса я столкнулся с проблемой перегрузки Scene :: addObject.

Чтобы повторить соответствующие биты и сделать это самостоятельносодержится с наименьшим возможным количеством деталей:

  • У меня есть иерархия объектов, наследующих от Interface, из которых есть Foo s и Bar s;
  • У меня естьScene, которому принадлежат эти объекты;
  • Foo s должно быть unique_ptr s, а Bar s должно быть shared_ptr s в моем главном (по причинам, объясненным в предыдущем вопросе);
  • main передает их экземпляру Scene, который становится владельцем.

Пример минимального кода 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_ptr s).

Я передаюоба по значению, потому что я хочу дать понять, что я беру в собственность Scene (и увеличиваю счетчик ссылок для shared_ptr s).Мне не совсем понятно, где проблема вообще, и я не смог найти ни одного примера в другом месте.

Ответы [ 5 ]

0 голосов
/ 26 ноября 2018

Решение от 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
}

Здесь мы сначала вводим некоторые вспомогательные классы, позволяющие перепривязывать умные указатели.

0 голосов
/ 26 ноября 2018

Foos должны быть unique_ptrs, а Bars должны быть shared_ptrs в моем основном (по причинам, объясненным в предыдущем вопросе);

Можете ли вы перегрузить с помощью указателя на Foo и указатель на Bar вместо указателя на Interface, так как вы хотите относиться к ним по-разному?

0 голосов
/ 26 ноября 2018

Другой ответ объясняет неоднозначность и возможное решение.Вот другой способ на случай, если вам понадобятся обе перегрузки;в таких случаях вы всегда можете добавить другой параметр, чтобы устранить неоднозначность и использовать диспетчеризацию тегов.Код таблички скрыт в приватной части 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{});
    }
};

Полный пример компиляции здесь .

0 голосов
/ 26 ноября 2018

Вы объявили две перегрузки, одна из которых принимает 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.Это предполагает, что две перегрузки имеют одинаковое поведение, если они не переименовывают, один из методов может быть более подходящим.

0 голосов
/ 26 ноября 2018

Проблема, с которой вы сталкиваетесь, заключается в том, что этот конструктор shared_ptr (13) (который не является явным) является таким же хорошим совпадением, как и аналогичный конструктор «Перемещение производных в базу» unique_ptr (6) (также не явно).

template< class Y, class Deleter > 
shared_ptr( std::unique_ptr<Y,Deleter>&& r ); // (13)

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)

6) Создает unique_ptr путем передачи права собственности с u на *this, где u создается с указаннымсредство удаления (E).

Этот конструктор участвует в разрешении перегрузки только в том случае, если все следующее верно:

a) unique_ptr<U, E>::pointer неявно преобразуется в указатель

b) U не являетсятип массива

c) Либо Deleter является ссылочным типом, а E совпадает с типом D, либо Deleter не является ссылочным типом, а E неявно преобразуется в D

В неполиморфном случае вы создаете unique_ptr<T> из unique_ptr<T>&&, который использует не шаблонный конструктор перемещения.Там разрешение перегрузки предпочитает не шаблон


Я собираюсь предположить, что Scene хранит shared_ptr<Interface> с.В этом случае вам не нужно перегружать addObject для unique_ptr, вы можете просто разрешить неявное преобразование в вызове.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...