次の方法で共有


TN016: MFC での C++ 多重継承の使用

このメモでは、Microsoft Foundation クラスで複数継承 (MI) を使用する方法について説明します。 MFC では MI の使用は必要ありません。 MI は MFC クラスでは使用されず、クラス ライブラリを記述する必要はありません。

次のサブトピックでは、MI が一般的な MFC イディオムの使用に与える影響と、MI の制限の一部について説明します。 これらの制限の一部は、一般的な C++ の制限です。 その他は MFC アーキテクチャによって適用されます。

このテクニカル ノートの最後には、MI を使用する完全な MFC アプリケーションがあります。

CRuntimeClass

MFC の永続化および動的オブジェクト作成メカニズムでは、 CRuntimeClass データ構造を使用してクラスを一意に識別します。 MFC は、これらの構造体の 1 つを、アプリケーション内の動的クラスまたはシリアル化可能なクラスに関連付けます。 これらの構造体は、 AFX_CLASSINIT型の特殊な静的オブジェクトを使用してアプリケーションが起動したときに初期化されます。

CRuntimeClassの現在の実装では、MI ランタイムの型情報はサポートされていません。 これは、MFC アプリケーションで MI を使用できないという意味ではありません。 ただし、複数の基底クラスを持つオブジェクトを操作する場合は、特定の責任があります。

CObject::IsKindOf メソッドは、複数の基底クラスがある場合、オブジェクトの型を正しく判断しません。 したがって、CObject を仮想基底クラスとして使用することはできません。また、CObject::Serialize や CObject::operator new などのCObjectメンバー関数のすべての呼び出しには、C++ が適切な関数呼び出しを明確に区別できるようにスコープ修飾子が必要です。 プログラムが MFC 内で MI を使用する場合、 CObject 基底クラスを含むクラスは、基底クラスの一覧で最も左にあるクラスである必要があります。

別の方法として、 dynamic_cast 演算子を使用します。 MI を持つオブジェクトを基底クラスのいずれかにキャストすると、コンパイラは指定された基底クラスの関数を強制的に使用します。 詳細については、「 dynamic_cast演算子」を参照してください。

CObject - すべてのクラスのルート

すべての重要なクラスは、クラス CObjectから直接または間接的に派生します。 CObject にはメンバー データはありませんが、いくつかの既定の機能があります。 MI を使用する場合は、通常、2 つ以上の CObject派生クラスから継承します。 次の例は、クラスが CFrameWndCObList から継承する方法を示しています。

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

この場合、 CObject は 2 回含まれます。 つまり、 CObject メソッドまたは演算子への参照を明確にする方法が必要です。 new 演算子delete 演算子は、あいまいさを解消する必要がある 2 つの演算子です。 別の例として、次のコードはコンパイル時にエラーを発生させます。

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

CObject メソッドの再実装

2 つ以上の CObject 派生基底クラスを持つ新しいクラスを作成する場合は、他のユーザーが使用する CObject メソッドを再実装する必要があります。 演算子 newdelete は必須であり、 ダンプ をお勧めします。 次の例では、 new 演算子と delete 演算子、および 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);
    }
    // ...
};

CObject の仮想継承

CObjectを実質的に継承すると、関数のあいまいさの問題が解決されると思われるかもしれませんが、そうではありません。 CObjectにメンバー データがないため、基底クラスメンバー データの複数のコピーを防ぐために仮想継承は必要ありません。 前に示した最初の例では、CFrameWndCObListで実装が異なるため、Dump仮想メソッドはあいまいです。 あいまいさを解消する最善の方法は、前のセクションで示した推奨事項に従う方法です。

CObject::IsKindOf と Run-Time Typing

CObjectの MFC でサポートされるランタイム型指定メカニズムでは、マクロのDECLARE_DYNAMIC、IMPLEMENT_DYNAMIC、DECLARE_DYNCREATE、IMPLEMENT_DYNCREATE、DECLARE_SERIAL、およびIMPLEMENT_SERIALが使用されます。 これらのマクロは、実行時の型チェックを実行して、安全なダウンキャストを保証できます。

これらのマクロは 1 つの基底クラスのみをサポートし、継承されたクラスを乗算する限られた方法で動作します。 IMPLEMENT_DYNAMICまたはIMPLEMENT_SERIALで指定する基底クラスは、最初の (または一番左の) 基底クラスである必要があります。 この配置により、左端の基底クラスに対してのみ型チェックを実行できます。 ランタイム型システムは、追加の基底クラスについて何も認識しません。 次の例では、ランタイム システムは CFrameWndに対して型チェックを実行しますが、 CObListについては何も認識しません。

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

CWnd とメッセージ マップ

MFC メッセージ マップ システムが正常に動作するには、次の 2 つの追加要件があります。

  • CWnd派生基底クラスは 1 つだけ必要です。

  • CWnd派生基底クラスは、最初の (または左端の) 基底クラスである必要があります。

動作しない例を次に示します。

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

MI を使用したサンプル プログラム

次の例は、 CFrameWndCWinApp から派生した 1 つのクラスで構成されるスタンドアロン アプリケーションです。 この方法でアプリケーションを構成することはお勧めしませんが、これは 1 つのクラスを持つ最小 MFC アプリケーションの例です。

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

こちらも参照ください

番号別テクニカル ノート
カテゴリ別テクニカル ノート