c++ - make_unique - shared_ptr example




El método de sobrecarga para unique_ptr y shared_ptr es ambiguo con el polimorfismo (4)

Codificando cosas después de tomar el consejo de la respuesta de mi pregunta anterior , me encontré con un problema con la sobrecarga de Scene :: addObject.

Para reiterar los bits relevantes y hacer esto autónomo, con el menor detalle posible:

  • Tengo una jerarquía de objetos heredados de la Interface de los cuales hay Foo s y Bar s;
  • Tengo una Scene que posee estos objetos;
  • Foo s debe ser unique_ptr s y Bar s debe ser shared_ptr s en mi principal (por las razones explicadas en la pregunta anterior);
  • El main pasa a la instancia de Scene , que toma posesión.

Ejemplo de código mínimo es 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);
}

Descomentar los resultados de las líneas comentadas en:

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)

      ^~~~~

Descomentar lo compartido y comentar las cosas únicas que también se compilan, así que supongo que el problema está, como dice el compilador, en la sobrecarga. Sin embargo, necesito la sobrecarga, ya que estos dos tipos deberán almacenarse en algún tipo de colección y, de hecho, se mantendrán como punteros a la base (posiblemente todos se shared_ptr movido a shared_ptr s).

Estoy pasando ambos valores por valor porque quiero aclarar que estoy tomando posesión de Scene (y estoy subiendo el contador de referencia para shared_ptr s). No me queda muy claro cuál es el problema, y ​​no pude encontrar ningún ejemplo de esto en ningún otro lugar.


Los Foos deben ser unique_ptrs y las Barras deben ser shared_ptrs en mi principal (por las razones explicadas en la pregunta anterior);

¿Puede sobrecargar en términos de puntero a Foo y puntero a Bar lugar de puntero a Interface , ya que desea tratarlos de manera diferente?


El problema con el que se encuentra es que este constructor de shared_ptr (13) , (que no es explícito), es una coincidencia tan buena como un constructor similar de "mover derivado a base" de unique_ptr (6) (tampoco es explícito).

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

13) Construye un shared_ptr que administra el objeto actualmente administrado por r . El marcador asociado con r se almacena para la eliminación futura del objeto gestionado. r gestiona ningún objeto después de la llamada.

Esta sobrecarga no participa en la resolución de sobrecarga si std::unique_ptr<Y, Deleter>::pointer no es compatible con T* . Si r.get() es un puntero nulo, esta sobrecarga es equivalente al constructor predeterminado (1). (desde C ++ 17)

template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept; //(6)

6) Construye un unique_ptr transfiriendo la propiedad de u a *this , donde u se construye con un deleter especificado (E).

Este constructor solo participa en la resolución de sobrecarga si todo lo siguiente es verdadero:

a) unique_ptr<U, E>::pointer es convertible implícitamente a puntero

b) U no es un tipo de matriz

c) O bien Deleter es un tipo de referencia y E es el mismo tipo que D , o Deleter no es un tipo de referencia y E es convertible implícitamente a D

En el caso no polimórfico, estás construyendo un unique_ptr<T> partir de un unique_ptr<T>&& , que utiliza el constructor de movimiento sin plantilla. Hay resolución de sobrecarga que prefiere la no-plantilla.

Voy a suponer que Scene almacena shared_ptr<Interface> s. En ese caso, no necesita sobrecargar addObject para unique_ptr , solo puede permitir la conversión implícita en la llamada.


La otra respuesta explica la ambigüedad y una posible solución. Aquí hay otra manera en caso de que termine necesitando ambas sobrecargas; siempre puede agregar otro parámetro en tales casos para romper la ambigüedad y usar el envío de etiquetas. El código de la placa de la caldera está oculto en la parte privada de la 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{});
    }
};

El ejemplo compilable completo está here.


La solución de jrok ya es bastante buena. El siguiente enfoque permite reutilizar el código aún mejor:

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

Lo que hacemos aquí es introducir primero algunas clases de ayuda que permiten volver a enlazar los punteros inteligentes.







unique-ptr