TN016: Usar la herencia múltiple de C++ con MFC
En esta nota, se describe cómo usar la herencia múltiple (MI) con Microsoft Foundation Classes. No es necesario usar la herencia múltiple con MFC. No se usa en ninguna clase MFC y no es necesaria para escribir una biblioteca de clases.
En los subtemas siguientes, se describe cómo afecta la herencia múltiple al uso de expresiones comunes de MFC, así como describir algunas de las restricciones de la herencia múltiple. Algunas de estas restricciones son restricciones generales de C++. Otras las impone la arquitectura de MFC.
Al final de esta nota técnica, encontrará una aplicación MFC completa que usa la herencia múltiple.
CRuntimeClass
Los mecanismos de creación de objetos dinámicos y de persistencia de MFC usan la estructura de datos CRuntimeClass para identificar las clases de manera única. MFC asocia una de estas estructuras a cada clase dinámica o serializable en la aplicación. Estas estructuras se inicializan cuando la aplicación se inicia mediante un objeto estático especial de tipo AFX_CLASSINIT
.
La implementación actual de CRuntimeClass
no admite la información de tipo herencia múltiple en tiempo de ejecución. Esto no significa que no puede usar la herencia múltiple en la aplicación MFC. Sin embargo, tendrá ciertas responsabilidades cuando trabaje con objetos que tengan más de una clase base.
El método CObject::IsKindOf no determinará correctamente el tipo de un objeto si tiene varias clases base. Por lo tanto, no puede usar CObject como una clase base virtual y todas las llamadas a funciones miembro CObject
, como CObject::Serialize y CObject::operator new, deben tener calificadores de ámbito para que C++ puede eliminar la ambigüedad de la llamada de función adecuada. Cuando un programa usa la herencia múltiple dentro de MFC, la clase que contiene la clase base CObject
debe ser la clase que se encuentra más a la izquierda en la lista de clases base.
Una alternativa es usar el operador dynamic_cast
. La conversión de un objeto con herencia múltiple en una de sus clases base obligará al compilador a utilizar las funciones de la clase base proporcionada. Para obtener más información, consulte el Operador dynamic_cast.
CObject: la raíz de todas las clases
Todas las clases significativas se derivan directa o indirectamente de la clase CObject
. CObject
no tiene ningún dato de miembro, pero sí cierta funcionalidad predeterminada. Por lo general, cuando se usa la herencia múltiple, se heredará de dos o más clases derivadas de CObject
. En el ejemplo siguiente, se muestra cómo una clase puede heredar de un CFrameWnd y un CObList:
class CListWnd : public CFrameWnd, public CObList
{
// ...
};
CListWnd myListWnd;
En este caso, CObject
se incluye dos veces. Esto significa que necesita una manera de eliminar la ambigüedad de cualquier referencia a operadores o métodos CObject
. operator new y operator delete son dos operadores cuya ambigüedad se debe eliminar. Como otro ejemplo, el código siguiente produce un error en tiempo de compilación:
myListWnd.Dump(afxDump); // compile time error, CFrameWnd::Dump or CObList::Dump
Reimplementación de métodos CObject
Al crear una clase que tiene dos o varias clases base CObject
derivadas, debe volver a implementar los métodos CObject
que quiere que otros usuarios utilicen. Los operadores new
y delete
son obligatorios y Dump, recomendado. En el ejemplo siguiente, se vuelven a implementar los operadores new
y delete
y el método Dump
:
class CListWnd : public CFrameWnd, public CObList
{
public:
void* operator new(size_t nSize)
{
return CFrameWnd:: operator new(nSize);
}
void operator delete(void* p)
{
CFrameWnd:: operator delete(p);
}
void Dump(CDumpContent& dc)
{
CFrameWnd::Dump(dc);
CObList::Dump(dc);
}
// ...
};
Herencia virtual de CObject
Podría parecer que la herencia virtual de CObject
resolvería el problema de ambigüedad de las funciones, pero no es el caso. Como no hay datos de miembro en CObject
, no necesita la herencia virtual para evitar varias copias de los datos de miembro de una clase base. En el primer ejemplo que se mostró anteriormente, el método virtual Dump
sigue siendo ambiguo porque se implementa de manera diferente en CFrameWnd
y CObList
. La mejor manera de quitar la ambigüedad es seguir las recomendaciones presentadas en la sección anterior.
CObject::IsKindOf y Run-Time Typing
El mecanismo de escritura en tiempo de ejecución compatible con MFC en CObject
usa las macros DECLARE_DYNAMIC, IMPLEMENT_DYNAMIC, DECLARE_DYNCREATE, IMPLEMENT_DYNCREATE, DECLARE_SERIAL y IMPLEMENT_SERIAL. Estas macros puede realizar una comprobación de tipos en tiempo de ejecución para garantizar la seguridad de las conversiones a tipos heredados.
Estas macros solo admiten una clase base única y funcionarán de manera limitada en el caso de las clases con herencia múltiple. La clase base que especifica en IMPLEMENT_DYNAMIC o IMPLEMENT_SERIAL debe ser la primera clase base (o la que está más a la izquierda). Esta colocación le permitirá realizar la comprobación de tipos solo para la clase base que está más a la izquierda. El sistema de tipos en tiempo de ejecución no se enterará de nada sobre las clases base adicionales. En el ejemplo siguiente, los sistemas en tiempo de ejecución realizarán la comprobación de tipos con CFrameWnd
, pero no sabrán nada sobre CObList
.
class CListWnd : public CFrameWnd, public CObList
{
DECLARE_DYNAMIC(CListWnd)
// ...
};
IMPLEMENT_DYNAMIC(CListWnd, CFrameWnd)
CWnd y asignaciones de mensajes
Para que el sistema de asignación de mensajes MFC funcione correctamente, hay dos requisitos adicionales:
Debe haber solo una clase base derivada de
CWnd
.La clase base derivada de
CWnd
debe ser la primera clase base (o la que se encuentra más a la izquierda).
Estos son algunos ejemplos que no funcionarán:
class CTwoWindows : public CFrameWnd, public CEdit
{ /* ... */ }; // error : two copies of CWnd
class CListEdit : public CObList, public CEdit
{ /* ... */ }; // error : CEdit (derived from CWnd) must be first
Un programa de ejemplo con herencia múltiple
En el ejemplo siguiente, se muestra una aplicación independiente que consta de una clase derivada de CFrameWnd
y CWinApp. No se recomienda estructurar una aplicación de esta manera, pero en este caso se trata de un ejemplo de la aplicación MFC más pequeña que tiene una clase.
#include <afxwin.h>
class CHelloAppAndFrame : public CFrameWnd, public CWinApp
{
public:
CHelloAppAndFrame() {}
// Necessary because of MI disambiguity
void* operator new(size_t nSize)
{ return CFrameWnd::operator new(nSize); }
void operator delete(void* p)
{ CFrameWnd::operator delete(p); }
// Implementation
// CWinApp overrides
virtual BOOL InitInstance();
// CFrameWnd overrides
virtual void PostNcDestroy();
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(CHelloAppAndFrame, CFrameWnd)
ON_WM_PAINT()
END_MESSAGE_MAP()
// because the frame window is not allocated on the heap, we must
// override PostNCDestroy not to delete the frame object
void CHelloAppAndFrame::PostNcDestroy()
{
// do nothing (do not call base class)
}
void CHelloAppAndFrame::OnPaint()
{
CPaintDC dc(this);
CRect rect;
GetClientRect(rect);
CString s = "Hello, Windows!";
dc.SetTextAlign(TA_BASELINE | TA_CENTER);
dc.SetTextColor(::GetSysColor(COLOR_WINDOWTEXT));
dc.SetBkMode(TRANSPARENT);
dc.TextOut(rect.right / 2, rect.bottom / 2, s);
}
// Application initialization
BOOL CHelloAppAndFrame::InitInstance()
{
// first create the main frame
if (!CFrameWnd::Create(NULL, "Multiple Inheritance Sample",
WS_OVERLAPPEDWINDOW, rectDefault))
return FALSE;
// the application object is also a frame window
m_pMainWnd = this;
ShowWindow(m_nCmdShow);
return TRUE;
}
CHelloAppAndFrame theHelloAppAndFrame;