Partager via


TN016 : Utilisation de l’héritage multiple C++ avec MFC

Cette note explique comment utiliser plusieurs héritages (MI) avec les classes Microsoft Foundation. L’utilisation de MI n’est pas requise avec MFC. MI n’est utilisé dans aucune classe MFC et n’est pas nécessaire pour écrire une bibliothèque de classes.

Les sous-rubriques suivantes décrivent comment mi affecte l’utilisation des idiomes MFC courants, ainsi que la couverture de certaines des restrictions de MI. Certaines de ces restrictions sont des restrictions C++ générales. D’autres sont imposées par l’architecture MFC.

À la fin de cette note technique, vous trouverez une application MFC complète qui utilise MI.

CRuntimeClass

Les mécanismes de persistance et de création d’objets dynamiques de MFC utilisent la structure de données CRuntimeClass pour identifier de manière unique les classes. MFC associe l’une de ces structures à chaque classe dynamique et/ou sérialisable dans votre application. Ces structures sont initialisées au démarrage de l’application à l’aide d’un objet statique spécial de type AFX_CLASSINIT.

L’implémentation actuelle de CRuntimeClass ne prend pas en charge les informations de type d’exécution MI. Cela ne signifie pas que vous ne pouvez pas utiliser mi dans votre application MFC. Toutefois, vous aurez certaines responsabilités lorsque vous travaillez avec des objets qui ont plusieurs classes de base.

La méthode CObject ::IsKindOf ne détermine pas correctement le type d’un objet s’il a plusieurs classes de base. Par conséquent, vous ne pouvez pas utiliser CObject comme classe de base virtuelle, et tous les appels à CObject des fonctions membres telles que CObject ::Serialize et CObject ::operator new doivent avoir des qualificateurs d’étendue pour que C++ puisse lever l’ambiguïté de l’appel de fonction approprié. Lorsqu’un programme utilise mi dans MFC, la classe qui contient la CObject classe de base doit être la classe la plus à gauche dans la liste des classes de base.

Une alternative consiste à utiliser l’opérateur dynamic_cast . Le cast d’un objet avec MI sur l’une de ses classes de base force le compilateur à utiliser les fonctions dans la classe de base fournie. Pour plus d’informations, consultez dynamic_cast Opérateur.

CObject - Racine de toutes les classes

Toutes les classes significatives dérivent directement ou indirectement de la classe CObject. CObject n’a pas de données membres, mais il dispose de certaines fonctionnalités par défaut. Lorsque vous utilisez mi, vous hériterez généralement de deux classes dérivées ou plus CObject. L’exemple suivant montre comment une classe peut hériter d’un CFrameWnd et d’un CObList :

class CListWnd : public CFrameWnd, public CObList
{
    // ...
};
CListWnd myListWnd;

Dans ce cas CObject , il est inclus deux fois. Cela signifie que vous avez besoin d’un moyen de désambiguer toute référence à des méthodes ou des CObject opérateurs. L’opérateur new and operator delete est deux opérateurs qui doivent être désambigués. Comme autre exemple, le code suivant provoque une erreur au moment de la compilation :

myListWnd.Dump(afxDump); // compile time error, CFrameWnd::Dump or CObList::Dump

Reimplementing CObject, méthodes

Lorsque vous créez une classe qui a deux classes de base dérivées ou plus CObject , vous devez réexémettre les CObject méthodes que vous souhaitez que d’autres personnes utilisent. Les opérateurs new et delete sont obligatoires et le vidage est recommandé. L’exemple suivant réexémente les new opérateurs et delete les opérateurs et la Dump méthode :

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);
    }
    // ...
};

Héritage virtuel de CObject

Il semblerait que pratiquement hériter CObject permettrait de résoudre le problème d’ambiguïté des fonctions, mais ce n’est pas le cas. Étant donné qu’il n’existe aucune donnée membre dans CObject, vous n’avez pas besoin d’héritage virtuel pour empêcher plusieurs copies d’une donnée membre de classe de base. Dans le premier exemple présenté précédemment, la Dump méthode virtuelle est toujours ambiguë, car elle est implémentée différemment dans CFrameWnd et CObList. La meilleure façon de supprimer l’ambiguïté consiste à suivre les recommandations présentées dans la section précédente.

CObject ::IsKindOf et Run-Time saisie

Le mécanisme de saisie au moment de l’exécution pris en charge par MFC CObject utilise les macros DECLARE_DYNAMIC, IMPLEMENT_DYNAMIC, DECLARE_DYNCREATE, IMPLEMENT_DYNCREATE, DECLARE_SERIAL et IMPLEMENT_SERIAL. Ces macros peuvent effectuer une vérification de type au moment de l’exécution pour garantir des prédiffusions sécurisées.

Ces macros ne prennent en charge qu’une seule classe de base et fonctionnent de manière limitée pour multiplier les classes héritées. La classe de base que vous spécifiez dans IMPLEMENT_DYNAMIC ou IMPLEMENT_SERIAL doit être la première classe de base (ou la plus à gauche). Ce placement vous permet d’effectuer uniquement la vérification de type pour la classe de base la plus à gauche. Le système de type d’exécution ne connaît pas les classes de base supplémentaires. Dans l’exemple suivant, les systèmes d’exécution effectuent une vérification de type par rapport CFrameWndà , mais ne savent rien sur CObList.

class CListWnd : public CFrameWnd, public CObList
{
    DECLARE_DYNAMIC(CListWnd)
    // ...
};
IMPLEMENT_DYNAMIC(CListWnd, CFrameWnd)

Cartes de messages et CWnd

Pour que le système de mappage de messages MFC fonctionne correctement, il existe deux exigences supplémentaires :

  • Il ne doit y avoir qu’une CWndseule classe de base dérivée.

  • La CWndclasse de base dérivée doit être la première classe de base (ou la plus à gauche).

Voici quelques exemples qui ne fonctionneront pas :

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

Exemple de programme utilisant MI

L’exemple suivant est une application autonome qui se compose d’une classe dérivée et CFrameWndde CWinApp. Nous vous déconseillons de structurer une application de cette façon, mais il s’agit d’un exemple d’application MFC la plus petite qui possède une classe.

#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;

Voir aussi

Notes techniques par numéro
Notes techniques par catégorie