Pregunta

Tengo un código como este:

class RetInterface {...}

class Ret1: public RetInterface {...}

class AInterface
{
  public:
     virtual boost::shared_ptr<RetInterface> get_r() const = 0;
     ...
};

class A1: public AInterface
{
  public:
     boost::shared_ptr<Ret1> get_r() const {...}
     ...
};

Este código no compila.

En visual studio se plantea

C2555:primordial función virtual tipo de retorno es diferente, y no es covariante

Si yo no lo uso boost::shared_ptr pero el retorno bruto de los punteros, el código se compila (entiendo que esto es debido a covariante tipos de devolución en C++).Puedo ver que el problema es debido a boost::shared_ptr de Ret1 no es derivada de la boost::shared_ptr de RetInterface.Pero quiero volver boost::shared_ptr de Ret1 para su uso en otras clases, de lo contrario debe convertir el valor devuelto después de la devolución.

  1. Estoy haciendo algo mal?
  2. Si no, ¿por qué es el lenguaje como este - que debe ser extensible a manejar la conversión entre los punteros inteligentes en este escenario?Hay una conveniente solución?
¿Fue útil?

Solución

En primer lugar, así es como funciona en C ++: el tipo de retorno de una función virtual en una clase derivada debe ser el mismo que en la clase base. Existe la excepción especial de que una función que devuelve una referencia / puntero a alguna clase X puede ser anulada por una función que devuelve una referencia / puntero a una clase que se deriva de X, pero como observa, esto no permite punteros inteligentes (como shared_ptr), solo para punteros simples.

Si su interfaz RetInterface es lo suficientemente completa, entonces no necesitará saber el tipo real devuelto en el código de llamada. En general, no tiene sentido de todos modos: la razón get_r es una función virtual en primer lugar es porque la llamará a través de un puntero o una referencia a la clase base AInterface, en cuyo caso puede No sé qué tipo devolvería la clase derivada. Si llama a esto con una referencia A1 real, puede crear una función get_r1 separada en <=> que haga lo que necesita.

class A1: public AInterface
{
  public:
     boost::shared_ptr<RetInterface> get_r() const
     {
         return get_r1();
     }
     boost::shared_ptr<Ret1> get_r1() const {...}
     ...
};

Alternativamente, puede usar el patrón de visitante o algo como mi técnica Dynamic Double Dispatch para pasar una devolución de llamada al objeto devuelto que luego puede invocar la devolución de llamada con el tipo correcto.

Otros consejos

¿Qué pasa con esta solución?

template<typename Derived, typename Base>
class SharedCovariant : public shared_ptr<Base>
{
public:

typedef Base BaseOf;

SharedCovariant(shared_ptr<Base> & container) :
    shared_ptr<Base>(container)
{
}

shared_ptr<Derived> operator ->()
{
    return boost::dynamic_pointer_cast<Derived>(*this);
}
};

por ejemplo:

struct A {};

struct B : A {};

struct Test
{
    shared_ptr<A> get() {return a_; }

    shared_ptr<A> a_;
};

typedef SharedCovariant<B,A> SharedBFromA;

struct TestDerived : Test
{
    SharedBFromA get() { return a_; }
};

No puede cambiar los tipos de retorno (para tipos de retorno sin puntero ni referencia) cuando se sobrecargan los métodos en C ++. A1::get_r debe devolver un boost::shared_ptr<RetInterface>.

Anthony Williams tiene una buena respuesta .

Aquí va mi intento :

template<class T>
class Child : public T
{
public:
    typedef T Parent;
};

template<typename _T>
class has_parent
{
private:
    typedef char                        One;
    typedef struct { char array[2]; }   Two;

    template<typename _C>
    static One test(typename _C::Parent *);
    template<typename _C>
    static Two test(...);

public:
    enum { value = (sizeof(test<_T>(nullptr)) == sizeof(One)) };
};

class A
{
public :
   virtual void print() = 0;
};

class B : public Child<A>
{
public:
   void print() override
   {
       printf("toto \n");
   }
};

template<class T, bool hasParent = has_parent<T>::value>
class ICovariantSharedPtr;

template<class T>
class ICovariantSharedPtr<T, true> : public ICovariantSharedPtr<typename T::Parent>
{
public:
   T * get() override = 0;
};

template<class T>
class ICovariantSharedPtr<T, false>
{
public:
    virtual T * get() = 0;
};

template<class T>
class CovariantSharedPtr : public ICovariantSharedPtr<T>
{
public:
    CovariantSharedPtr(){}

    CovariantSharedPtr(std::shared_ptr<T> a_ptr) : m_ptr(std::move(a_ptr)){}

    T * get() final
   {
        return m_ptr.get();
   }
private:
    std::shared_ptr<T> m_ptr;
};

Y un poco de ejemplo :

class UseA
{
public:
    virtual ICovariantSharedPtr<A> & GetPtr() = 0;
};

class UseB : public UseA
{
public:
    CovariantSharedPtr<B> & GetPtr() final
    {
        return m_ptrB;
    }
private:
    CovariantSharedPtr<B> m_ptrB = std::make_shared<B>();
};

int _tmain(int argc, _TCHAR* argv[])
{
    UseB b;
    UseA & a = b;
    a.GetPtr().get()->print();
}

Explicaciones :

Esta solución implica meta-progamming y modificar las clases que se utilizan en covariante de punteros inteligentes.

La simple estructura de la plantilla Child está aquí para enlazar el tipo de Parent y la herencia.Cualquier clase que herede de Child<T> van a heredar de T y definir T como Parent.Las clases utilizadas en covariante de punteros inteligentes necesidades de este tipo para ser definido.

La clase has_parent se utiliza para detectar en tiempo de compilación si una clase se define el tipo de Parent o no.Esta parte no es mía, he utilizado el mismo código para detectar si existe un método (ver aquí)

Como queremos que la covarianza con punteros inteligentes, queremos que nuestros punteros inteligentes para imitar la clase existente en la arquitectura.Es más fácil explicar cómo funciona en el ejemplo.

Cuando un CovariantSharedPtr<B> se define, se hereda de ICovariantSharedPtr<B>, el cual es interpretado como ICovariantSharedPtr<B, has_parent<B>::value>.Como B hereda de Child<A>, has_parent<B>::value es cierto, así que ICovariantSharedPtr<B> es ICovariantSharedPtr<B, true> y se hereda de ICovariantSharedPtr<B::Parent> que es ICovariantSharedPtr<A>.Como A no tiene Parent definido, has_parent<A>::value es falso, ICovariantSharedPtr<A> es ICovariantSharedPtr<A, false> y se hereda de nada.

El punto principal es como Bhereda de A, hemos ICovariantSharedPtr<B>la herencia de ICovariantSharedPtr<A>.Por lo que cualquier método devuelve un puntero o una referencia en ICovariantSharedPtr<A> puede ser sobrecargado por un método de devolución de la misma en ICovariantSharedPtr<B>.

Hay una solución ordenada publicada en esta publicación de blog (de Raoul Borges)

Un extracto del bit antes de agregar soporte para la herencia múltiple y los métodos abstractos es:

template <typename Derived, typename Base>
class clone_inherit<Derived, Base> : public Base
{
public:
   std::unique_ptr<Derived> clone() const
   {
      return std::unique_ptr<Derived>(static_cast<Derived *>(this->clone_impl()));
   }

private:
   virtual clone_inherit * clone_impl() const override
   {
      return new Derived(*this);
   }
};

class concrete: public clone_inherit<concrete, cloneable>
{
};

int main()
{
   std::unique_ptr<concrete> c = std::make_unique<concrete>();
   std::unique_ptr<concrete> cc = b->clone();

   cloneable * p = c.get();
   std::unique_ptr<clonable> pp = p->clone();
}

Yo recomendaría leer el artículo completo. Es simplemente escrito y bien explicado.

Mr Fooz respondió la parte 1 de su pregunta. Parte 2, funciona de esta manera porque el compilador no sabe si llamará AInterface :: get_r o A1 :: get_r en tiempo de compilación; necesita saber qué valor de retorno obtendrá, por lo que insiste en ambos métodos devolviendo el mismo tipo. Esto es parte de la especificación C ++.

Para la solución alternativa, si A1 :: get_r devuelve un puntero a RetInterface, los métodos virtuales en RetInterface seguirán funcionando como se esperaba y el objeto adecuado se eliminará cuando se destruya el puntero. No hay necesidad de diferentes tipos de devolución.

tal vez podría usar un parámetro out para evitar " covarianza con impulso devuelto shared_ptrs.

 void get_r_to(boost::shared_ptr<RetInterface>& ) ...

ya que sospecho que una persona que llama puede colocar un tipo shared_ptr más refinado como argumento.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top