¿Cómo puedo usar tipos de retorno covariantes con punteros inteligentes?

Tengo un código como este:

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

Este código no se comstack.

En el estudio visual aumenta

C2555: el tipo de retorno de función virtual anulable difiere y no es covariante

Si no uso boost::shared_ptr pero devuelvo punteros sin procesar, el código se comstack (entiendo que esto se debe a los tipos de retorno covariantes en C ++). Puedo ver que el problema es porque boost::shared_ptr de Ret1 no se deriva de boost::shared_ptr de RetInterface . Pero quiero devolver boost::shared_ptr de Ret1 para usar en otras clases, de lo contrario, debo convertir el valor devuelto después de la devolución.

  1. ¿Estoy haciendo algo mal?
  2. De lo contrario, ¿por qué el lenguaje es así? ¿Debería ser extensible para manejar la conversión entre punteros inteligentes en este escenario? ¿Hay una solución deseable?

    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 se nota, esto no permite punteros inteligentes. (como shared_ptr ), solo para los indicadores simples.

    Si su interfaz RetInterface es 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 por la cual get_r es una función virtual , en primer lugar, se debe a que la AInterface través de un puntero o referencia a la clase base AInterface , en cuyo caso no puedes saber de qué tipo clase derivada volvería. Si está llamando a esto con una referencia A1 real, puede simplemente crear una función get_r1 separada en A1 que haga lo que necesita.

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

    Alternativamente, puede usar el patrón de visitante o algo así como mi técnica de Despacho dynamic doble para pasar una callback al objeto devuelto que puede invocar la callback con el tipo correcto.

    ¿Qué pasa con esta solución?

     template class SharedCovariant : public shared_ptr { public: typedef Base BaseOf; SharedCovariant(shared_ptr & container) : shared_ptr(container) { } shared_ptr operator ->() { return boost::dynamic_pointer_cast(*this); } }; 

    p.ej:

     struct A {}; struct B : A {}; struct Test { shared_ptr get() {return a_; } shared_ptr a_; }; typedef SharedCovariant SharedBFromA; struct TestDerived : Test { SharedBFromA get() { return a_; } }; 

    No puede cambiar los tipos de devolución (para los tipos de devolución no puntero ni de referencia) al sobrecargar métodos en C ++. A1::get_r debe devolver un boost::shared_ptr .

    Anthony Williams tiene una buena respuesta integral.

    Aquí está mi bash:

     template class Child : public T { public: typedef T Parent; }; template class has_parent { private: typedef char One; typedef struct { char array[2]; } Two; template static One test(typename _C::Parent *); template static Two test(...); public: enum { value = (sizeof(test<_t>(nullptr)) == sizeof(One)) }; }; class A { public : virtual void print() = 0; }; class B : public Child { public: void print() override { printf("toto \n"); } }; template::value> class ICovariantSharedPtr; template class ICovariantSharedPtr : public ICovariantSharedPtr { public: T * get() override = 0; }; template class ICovariantSharedPtr { public: virtual T * get() = 0; }; template class CovariantSharedPtr : public ICovariantSharedPtr { public: CovariantSharedPtr(){} CovariantSharedPtr(std::shared_ptr a_ptr) : m_ptr(std::move(a_ptr)){} T * get() final { return m_ptr.get(); } private: std::shared_ptr m_ptr; }; 

    Y un pequeño ejemplo:

     class UseA { public: virtual ICovariantSharedPtr & GetPtr() = 0; }; class UseB : public UseA { public: CovariantSharedPtr & GetPtr() final { return m_ptrB; } private: CovariantSharedPtr m_ptrB = std::make_shared(); }; int _tmain(int argc, _TCHAR* argv[]) { UseB b; UseA & a = b; a.GetPtr().get()->print(); } 

    Explicaciones

    Esta solución implica metaprogtwigción y modificar las clases utilizadas en punteros inteligentes covariantes.

    La plantilla simple struct Child está aquí para enlazar el tipo Parent y inheritance. Cualquier clase que herede de Child heredará de T y definirá T como principal. Las clases utilizadas en punteros inteligentes covariantes necesitan este tipo para definirse.

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

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

    Cuando se define CovariantSharedPtr , hereda de ICovariantSharedPtr , que se interpreta como ICovariantSharedPtr::value> . Como B hereda de Child , has_parent::value es verdadero, entonces ICovariantSharedPtr es ICovariantSharedPtr y hereda de ICovariantSharedPtr que es ICovariantSharedPtr . Como A no tiene ninguno definido, has_parent::value es falso, ICovariantSharedPtr es ICovariantSharedPtr y hereda de nada.

    El punto principal es que B hereda de A , tenemos ICovariantSharedPtr hereda de ICovariantSharedPtr . Por lo tanto, cualquier método que devuelva un puntero o una referencia en ICovariantSharedPtr puede sobrecargarse mediante un método que devuelva el mismo en ICovariantSharedPtr .

    El Sr. Fooz respondió la primera parte de su pregunta. Parte 2, funciona de esta manera porque el comstackdor no sabe si llamará a AInterface :: get_r o A1 :: get_r en tiempo de comstackción; necesita saber qué valor de retorno va a obtener, 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ías usar un parámetro de salida para evitar la “covarianza con el boost devuelto shared_ptrs”.

      void get_r_to(boost::shared_ptr& ) ... 

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