Share via


テクニカル ノート 16: MFC における C++ の多重継承

このメモでは、Microsoft Foundation Classes を使用して、複数の継承 (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::SerializeCObject::operator new などの CObject メンバー関数へのすべての呼び出しには、C++ で適切な関数呼び出しをあいまいにするため、スコープ修飾子が必要です。 プログラムが MFC 内で MI を使用する場合、CObject 基本クラスを含むクラスは、基本クラスの一覧の一番左のクラスである必要があります。

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

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

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

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

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

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

CObject メソッドの再適用

2 つ以上のCObject派生基本クラスを持つ新しいクラスを作成する場合は、他のユーザーが使用する CObject メソッドを再実装する必要があります。 演算子 newdelete は必須であり、Dump をお勧めします。 次の例では、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 にはメンバー データがないので、基本クラスのメンバー データの複数のコピーを防ぐために仮想継承は必要はありません。 前に示した最初の例では、Dump 仮想メソッドは CFrameWndCObList で異なる方法で実装されたため、あいまいなままです。 あいまい性を取り除くには、前のセクションで示した推奨事項に従うのが最善です。

CObject::IsKindOf と実行時の型指定

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;

関連項目

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