2010-03-09 16 views
12

Tengo una jerarquía de clases bastante compleja en la que las clases son cruzadas dependiendo de cada una: Hay dos clases abstractas A y C que contienen un método que devuelve una instancia de C y A, respectivamente. En sus clases heredadas, quiero usar un tipo de covariante, que en este caso es un problema, ya que no conozco una forma de reenviar: declarar la relación de herencia.C++: ¿Cómo puedo evitar el "tipo de retorno covariante no válido" en clases heredadas sin conversión?

Obtengo un "test.cpp: 22: error: tipo de retorno de covariante no válido para 'virtual D * B :: outC()'" - error ya que el compilador no sabe que D es una subclase de C.

class C; 

class A { 
public: 
     virtual C* outC() = 0; 
}; 

class C { 
public: 
     virtual A* outA() = 0; 
}; 


class D; 

class B : public A { 
public: 
     D* outC(); 
}; 

class D : public C { 
public: 
     B* outA(); 
}; 

D* B::outC() { 
     return new D(); 
} 

B* D::outA() { 
     return new B(); 
} 

Si cambio el tipo de retorno de B :: outC() a C *, el ejemplo se compila. ¿Hay alguna manera de mantener B * y D * como tipos de devolución en las clases heredadas (sería intuitivo para mí que haya una manera)?

+2

¿Realmente necesitas ese tipo de enredo? (Llamarlo acoplamiento podría ser un poco corto) –

+0

A veces no es un problema de lenguaje, es un problema con la forma en que tratamos de usarlo. Si sus dos jerarquías están tan profundamente acopladas, creo que sería mejor con una jerarquía (fusionando 'A' con' C' y 'B' con' D') ya que parece que no pueden funcionar sin la una a la otra de todos modos . –

+0

Bueno, tengo dos tipos de clases: una especificación para una tarea y un ejecutor que realmente ejecuta la tarea (inicia varios hilos). La especificación en sí debe ser una fábrica para la tarea y cada ejecutor debe acceder a sus especificaciones. Y hay varias especificaciones con sus tareas. Entonces el ejecutor ajusta una especificación, por lo que el acoplamiento es solo fuerte en una dirección, en la otra dirección es solo el método de fábrica. – Searles

Respuesta

7

No conozco ninguna forma de tener miembros covariantes directamente acoplados en C++. Tendrás que añadir una capa o implementar tú mismo el retorno covariante.

Para la primera opción

class C; 

class A { 
public: 
     virtual C* outC() = 0; 
}; 

class C { 
public: 
     virtual A* outA() = 0; 
}; 


class BI : public A { 
public: 
}; 

class D : public C { 
public: 
     BI* outA(); 
}; 

class B: public BI { 
public: 
     D* outC(); 
}; 

D* B::outC() { 
     return new D(); 
} 

BI* D::outA() { 
     return new B(); 
} 

y para la segunda

class C; 

class A { 
public: 
     C* outC() { return do_outC(); } 
     virtual C* do_outC() = 0; 
}; 

class C { 
public: 
     virtual A* outA() = 0; 
}; 


class D; 

class B : public A { 
public: 
     D* outC(); 
     virtual C* do_outC(); 
}; 

class D : public C { 
public: 
     B* outA(); 
}; 

D* B::outC() { 
     return static_cast<D*>(do_outC()); 
} 

C* B::do_outC() { 
     return new D(); 
} 

B* D::outA() { 
     return new B(); 
} 

Tenga en cuenta que esta segunda opción es lo que se hace implícitamente por el compilador (con algunas comprobaciones estáticas que el static_cast es válida) .

0

No puede hacer esto debido a las expectativas del lado del cliente. Al usar una instancia C, no puede decir qué tipo de C es (una D u otra cosa). Por lo tanto, si almacena el puntero B (resultante de una llamada a la clase derivada pero no lo conoce en tiempo de compilación) en un puntero A, no estoy seguro de que todas las cosas de la memoria sean correctas.

Cuando llama a un método en un tipo polimórfico, el entorno de tiempo de ejecución tiene que verificar el tipo dinámico del objeto y mueve los punteros para adaptarse a su jerarquía de clases. No estoy seguro de que deba confiar en la covarianza. Eche un vistazo a this

+1

Has malentendido. Los tipos de retorno covariantes son * permitidos *. Si el cliente espera un puntero 'A', pero le das un puntero 'B', eso es porque todas las instancias de 'B' * son * instancias de' A' también. El problema en este caso es que el compilador no puede verificar en la declaración de 'B :: outC' que el nuevo tipo de devolución' D' es un descendiente del tipo de devolución original 'C'. Si el compilador hubiera podido ver la definición completa de 'D', habría permitido la nueva firma. Tenga en cuenta que el compilador no emite quejas sobre 'D :: outA' porque sabe que' C' es un descendiente de 'A'. –

+1

En mi ejemplo, el tipo de devolución co-variante en la clase D no es un problema. – Searles

4

Hasta donde yo sé, no hay forma de hacerlo sin una conversión explícita. El problema es que la definición de la clase B no puede saber que D es una subclase de C hasta que ve una definición completa de la clase D, pero la definición de clase D no puede saber que B es una subclase de A hasta que se ve una definición completa de la clase B, por lo que tiene una dependencia circular. Esto no se puede resolver con forward-declarations porque desafortunadamente una declaración forward no puede especificar una relación de herencia.

Hay un problema similar al tratar de implementar un método coherente clone() utilizando plantillas, which I found can be solved, pero la solución análoga sigue fallando porque la referencia circular sigue siendo imposible de resolver.

+0

Este es de hecho un problema de dependencia circular. Lo mismo aplica cuando intentas usar typedefs, enums, campos, etc., dependientes de la cruz +1 – doc

+0

Todo el mundo ha sido mordido por este problema de 'clonación' ... Lo que no me gusta de la solución es que presenta la parte de envoltura difícil, 'typedef' no se puede anunciar de antemano y es una pena que sus clientes tengan que saber que no deben usar el nombre no decorado: / –

Cuestiones relacionadas