Cette note décrit comment utiliser l'héritage Multiple (MI) avec la Microsoft Foundation Classes.
Pourquoi héritage Multiple?
Il y a un débat en cours dans le C++ et orientée-objet des communautés sur la valeur de MI. L'environnement de développement et le compilateur Visual C++ prend entièrement en charge MI.
La bibliothèque de classes MFC a été conçue afin que vous n'avez pas besoin de comprendre MI utiliser MFC. MI n'est pas utilisé dans les classes MFC. Nous avons trouvé que le MI n'est pas nécessaire d'écrire une bibliothèque de classes, ni est il nécessaire pour écrire des applications sérieuses. Utilisation de MI ou non, peut être une décision personnelle, donc nous quittons cette décision vous.
Si vous souhaitez utiliser MI?
Si vous comprenez déjà comment utiliser MI, comprendre les compromis performance et souhaitez utiliser MFC, cette technote va vous dire ce que vous devez faire. Certaines de ces restrictions sont des restrictions générales de C++, d'autres sont imposées par l'architecture de la MFC.
Ce qui suit décrit certaines des questions techniques de comment MI affectent l'utilisation des idiomes MFC communes. À la fin de cette note technique, une complète application MFC à l'aide de MI est inclus qui vous pouvez extraire et compiler.
CRuntimeClass
La persistance et les mécanismes de création dynamique d'objet de MFC utilisent la structure de données CRuntimeClass à identifier de manière unique les classes. MFC associe une structure de ce type à chaque classe dynamique ou sérialisable dans l'application. Ces structures sont initialisés au démarrage application à l'aide d'un objet statique spécial de type AFX_CLASSINIT. Vous ne devez pas concernent vous-même avec la mise en oeuvre de cette information, comme il est susceptible de changer entre les révisions des MFC.
L'implémentation actuelle de CRuntimeClass ne supporte pas d'héritage Multiple runtime type information. Cela ne signifie pas que vous ne pouvez pas utiliser MI dans votre application MFC, mais si vous le faites, vous avez certaines responsabilités lorsque vous travaillez avec des objets qui ont plus d'une classe de base.
La fonction de membre CObject::IsKindOf pas correctement déterminera le type d'un objet si elle a plusieurs classes de base. Par conséquent, vous ne pouvez utiliser CObject comme classe de base virtuelle, et tous les appels aux fonctions de membre de CObject comme Serialize et opérateur nouveau devront avoir des qualificateurs portée afin que C++ peut lever l'ambiguïté sur l'appel de fonction appropriée. Si vous ne trouvez pas la nécessité d'utiliser MI dans les MFC, alors vous devriez être sûr de faire de la classe contenant la classe de base de CObject la classe plus à gauche dans la liste des classes de base.
Pour obtenir des conseils sur l'utilisation et l'abus de MI, voir Styles de programmation de C++ avancé et idiomes par James O. Coplien (Addison Wesley, 1992).
CObject - la racine de toutes les Classes
Comme vous le savez, toutes les classes importantes dérivent directement ou indirectement de la classe de CObject. CObject n'a pas toutes les données membres, mais possède certaines fonctionnalités par défaut. Lors de l'utilisation de MI, il est commun d'hériter de deux ou plusieurs CObject-dérivées des classes, par exemple, une classe CFrameWnd et un CObList:
class CListWnd : public CFrameWnd, public CObList
{
...
};
CListWnd myListWnd
Dans ce cas CObject est inclus deux fois, qui conduit à deux problèmes:
myListW&nd.Dump(afxDump) ;
nbsp ; / / compiler erreur, CFrameWnd::Dump ou CObList::Dump
Mesures recommandées
Lors de la création d'une nouvelle classe avec deux ou plusieurs CObject dérivées de classes de base, implémenter de nouveau ces membre de CObject que vous prévoyez utiliser. Opérateurs new et delete sont obligatoires, Dump est recommandé. Par exemple:
class CListWnd : public CFrameWnd, public CObList
{
public :
nbsp ; VOID * opérateur new (size_t nSize)
{retour CFrameWnd::operator new(nSize);}
VOID operator delete (void * p)
{CFrameWnd::operator delete(p);}
VOID Dump (CDumpContent & dc)
{CFrameWnd::Dump(dc) ;
CObList::Dump(dc) ; }
...
}
Héritage virtuel de CObject?
Vous pouvez demander, « si vous héritez CObject pratiquement, ne tous les problèmes d'ambiguïté disparaissent? ».
Même dans le modèle d'objet Microsoft efficace, l'héritage virtuel n'est pas aussi efficace comme héritage non virtuel (tout comme l'héritage Multiple n'est pas aussi efficace que simple héritage dans certains cas). Puisqu'il n'y a pas de données membre de CObject, héritage virtuel n'est pas nécessaire pour empêcher les copies multiples des données des membres de la classe de base.
La vraie réponse est non, héritage virtuel ne résoudra pas les problèmes d'ambiguïté illustrées ci-dessus. Par exemple : la fonction membre virtuelle Dump est toujours ambiguë (puisque CFrameWnd et CObList appliquer différemment).
Nous recommandons donc suivant les étapes ci-dessus, d'homonymie:
CObject::IsKindOf et en tapant Run-time
Le mécanisme de frappe de runtime pris en charge par les MFC de CObject utilise les macros DECLARE_DYNAMIC, IMPLEMENT_DYNAMIC, DECLARE_DYNCREATE, IMPLEMENT_DYNCREATE, DECLARE_SERIAL et IMPLEMENT_SERIAL. Ceux-ci donnent la possibilité de faire une vérification de run-time type permettant de sécurité cast-downs.
Ces macros seulement soutiennent une seule classe de base et fonctionneront de façon limitée pour les classes héritées de multiplier. La classe de base que vous spécifiez dans IMPLEMENT_DYNAMIC ou IMPLEMENT_SERIAL devrait être la classe de base première (ou la plus à gauche). Par exemple,
class CListWnd : public CFrameWnd, public CObList
{
nbsp ; DECLARE_DY&NAMIC(CListWnd)
...
};
IMPLEMENT_DYNAMIC (CListWnd, CFrameWnd)
Cela vous permettra d'effectuer un type pour la classe de base plus à gauche seulement la vérification. Le système de run-time type sera ne savent rien de bases supplémentaires (CObList dans ce cas).
CWnd et cartes Message
Pour que le système de carte de messages MFC fonctionne correctement, il y a deux conditions supplémentaires:
Dans l'exemple ci-dessus, la classe CFrameWnd est la classe de base de la première.
Voici quelques exemples qui ne fonctionnent pas:
class CTwoWi&ndows : public CFrameWnd, CEdit public
nbsp ; { ... };
/ / erreur : deux copies de CWnd
Class CListEdit : public CObList, CEdit public
{ ... };
/ / erreur : CEdit (dérivée de CWnd) doit être le premier
Un exemple de programme à l'aide de MI
L'exemple suivant est une application autonome qui consiste en une classe dérivée de CFrameWnd et CWinApp. Cette façon de structurer une application n'est pas un recommandé, mais il s'agit d'un exemple de l'application MFC plus petit avec une seule classe.
Vous pouvez couper le programme suivant et copier sur le dessus de HELLOAPP.RPC dans l'exemple général MFC héritage simple HELLOAPP. Puis bâtir le programme comme vous le feriez normalement.
#include <afxwin.h>
class CHelloAppAndFrame : public CFrameWnd, public CWinApp
{
public:
CHelloAppAndFrame()
{ }
// Necessary evil for 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()
// since 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;
&Notes techniques par le numéro |nbsp ; Notes techniques par catégorie