Es una situación relativamente habitual que dos clases se tengan que conocer entre sí, es lo que se conoce como referencia circular. Aunque en principio debería sospechar de esta situación por el alto acoplamiento que introduce entre ambas clases, hay veces, en que puede ser excesivo crear una clase para que maneje la comunicación entre las clases. De todos modos si decidís usar este enfoque, una buena implementación es utilizar el patrón mediator Mediator.
Cuando esto nos ocurre en lenguajes como C# no hay problema, porque no existe el concepto de declaración e implementación de una clase separadas. Pero en C++ toda clase debe conocer la declaración de otra clase que quiera utilizar y el compilador debe ser capaz de encontrar la implementación. El problema es que si A conoce ve y B conoce A cuando el compilador llega a la compilación de la primera de ellas que encuentra en nuestro código no puede saber que es la segunda. Recordemos que la compilación en C++ se hace modulo a modulo y que es el linkado el que pone todo junto. Los errores suelen ser del estilo:
Error 2 error C2065: 'FChild' : undeclared identifier FParent.h 76
Error 3 error C2065: 'child' : undeclared identifier FParent.h 76
Error 4 error C2061: syntax error : identifier 'FChild' FParent.h 76
Error 5 error C2227: left of '->MdiParent' must point to class/struct/union/generic type FParent.h 77
Error 6 error C2227: left of '->Show' must point to class/struct/union/generic type FParent.h 78
El mecanismo habitual para resolver esta situación es utilizar "forward declaration" o declaración adelantada. Hasta aquí sin problema.
#pragma once
#include "B.h"
ref class B; //forward declaration
ref class A
{
public:
B^ b;
};
Más sobre "forward declaration" en las siguientes entradas de C++ FAQ Lite:
El problema surge cuando utilizamos C++/CLI y Visual Studio, y queremos comunicar dos formularios, situación habitual fruto de la necesidad de que un formulario se comunique con su padre en interfaces MDI (la propiedad MDIParent devuelve una referencia al padre y el padre suele ser quien crea el formulario hijo) o que el formulario lanzado obtenga datos de su lanzador y viceversa. Son claros casos de referencia circular.
Bueno pues siguiendo con lo expuesto anteriormente intentamos resolver la referencia circular utilizando "forward declaration" y… NO FUNCIONA!!!!. Obtenemos errores del tipo:
El problema es que el diseñador de formularios de Visual C++ 2005 solo es capaz de trabajar con clases de formulario definidas en el archivo .h (definiciones inline), si separamos la clase en .h y .cpp, el diseñador deja de funcionar. Luego tenemos que conseguir que funcione la predeclaración usando funciones inline.
Tal y como se puede leer en la faq [39.13] el orden de las declaraciones es critico cuando usamos "forward declaration" con funciones inline. Esto es fácil si ambas clases se definen e implementan en el mismo archivo .h, pero esto no suele ser viable. Pues bien el truco es cambiar la posición donde se realizan los includes, para que las clases de definan en el orden adecuado:
/* Archivo A.h */
#pragma once
ref class B;
ref class A
{
public:
A(void) {}
void GetB();
};
#include "B.h"
inline void A::GetB()
{
B^ b = gcnew B();
}
/* Archivo B.h */
#pragma once
ref class A;
ref class B
{
public:
B(void){}
A^ GetA();
};
#include "A.h"
inline A^ B::GetA()
{
return gcnew A();
}
Podéis descargar un ejemplo referencias circulares C++/CLI, basado en WinForms.
Gracias por este articulo (y gracias tambien a RFOG), esto nos ayuda mucho a los que estamos aprendiendo.
De esto se trata!!! La verdad es que el amigo RFOG es quien vio la importancia del tema y yo luego perfeccione la técnica.
esto se puede utilizar en todos los casos?
TENGO UNA PREGUNTA.COMO PUEDO HACER QUE EL FORMULARIO HIJO SEA DE TIPO MODAL? HE VISTO QUE ES IMPOSIBLE USAR SHOWDIALOG
NECESITO ACCEDER A LAS PROPIEDADES DEL PADRE, Y SI NO LO HAGO CON FORMULARIOS MIDI ES IMPOSIBLE YA QUE NO ME APARECEN ASI LAS PONGA COMO PUBLICAS
ESPERO SU RESPUESTA GRACIAS