2008-10-13 21 views
52

que tienen código como este:¿Cómo puedo usar tipos de retorno covariantes con punteros inteligentes?

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 el estudio visual que plantea

C2555: difiere del tipo de devolución de funciones virtuales primordiales y no es covariante

Si yo no uso boost::shared_ptr pero devuelven punteros primas, el código se compila (Entiendo que esto se debe a covariant return types en C++). Puedo ver que el problema se debe a que 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. Si no es así, ¿por qué el lenguaje es así? ¿Debe ser extensible manejar la conversión entre punteros inteligentes en este escenario? ¿Hay una solución deseable?
+0

SI no usas boost :: shared_ptr, ¿devuelves punteros? ¿Es administrado C++? – Lev

+0

@Lev Si intento devolver punteros sin procesar, el código se compila, pero luego hay un problema de administración de memoria. No, no estoy usando C++ administrado. –

+0

Lo que hago es: devolver punteros sin procesar pero documentar que la persona que llama es responsable de ajustar el puntero en un puntero inteligente, p. 'std :: unique_ptr (obj.clone())'. –

Respuesta

20

En primer lugar, así es como funciona en C++: el tipo de devolución 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 deriva de X, pero como se nota, esto no permite inteligente punteros (como shared_ptr), solo para punteros 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: el motivo get_r es una función virtual, en primer lugar porque se lo llamará por un puntero o referencia a la clase base AInterface, en cuyo caso no puede saber de qué tipo clase derivada volvería. Si llama a esto con una referencia real de A1, puede crear una función get_r1 separada en A1 que hace 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 {...} 
    ... 
}; 

Como alternativa, puede utilizar el patrón de visitante o algo así como mi técnica Dynamic Double Dispatch para pasar a una devolución de llamada en el objeto devuelto que puede invocar la respuesta con el tipo correcto.

+0

Will boost :: shared_ptr se convierte automáticamente a boost :: shared_ptr ? ¿No necesitas al menos algo como boost :: static_pointer_cast? – h9uest

+4

Devolver el puntero a la clase derivada tiene sentido con los métodos de clonación en las clases que implementan múltiples interfaces. –

1

No se pueden cambiar los tipos de devolución (para los tipos de devolución sin referencia ni referencia) al sobrecargar métodos en C++. A1::get_r debe devolver un boost::shared_ptr<RetInterface>.

Anthony Williams tiene un bonito completo answer.

-1

Mr Fooz respondió la parte 1 de su pregunta. Parte 2, funciona de esta manera porque el compilador no sabe si llamará a AInterface :: get_r o A1 :: get_r en tiempo de compilació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.

-1

tal vez usted podría utilizar un parámetro de salida de moverse "covarianza con shared_ptrs impulsar devueltos.

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

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

+0

Desafortunadamente no. –

1

¿Qué hay de 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_; } 
}; 
+0

¿No usó 'virtual'? –

1

Aquí es 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 pequeño 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-programación y modificar las clases utilizadas en punteros inteligentes covariantes .

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

La clase has_parent se utiliza para detectar en tiempo de compilació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 (see here)

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

Cuando se define CovariantSharedPtr<B>, hereda de ICovariantSharedPtr<B>, lo que se interpreta como ICovariantSharedPtr<B, has_parent<B>::value>. Como B hereda de Child<A>, has_parent<B>::value es verdadero, entonces ICovariantSharedPtr<B> es ICovariantSharedPtr<B, true> y 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 hereda de la nada.

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

+0

¿Qué tal si agregamos algunos fundamentos y explicaciones para aquellos que tienen poco tiempo para descifrar esto? ¿Cómo se compara con Morabot? Merci. – Quartz

Cuestiones relacionadas