このメモでは、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
派生クラスから継承します。 次の例は、クラスが CFrameWnd と CObList から継承する方法を示しています。
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
メソッドを再実装する必要があります。 演算子 new
と delete
は必須であり、 ダンプ をお勧めします。 次の例では、 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
にメンバー データがないため、基底クラスメンバー データの複数のコピーを防ぐために仮想継承は必要ありません。 前に示した最初の例では、CFrameWnd
とCObList
で実装が異なるため、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 を使用したサンプル プログラム
次の例は、 CFrameWnd
と CWinApp から派生した 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;