TN016: MFC에서 C++ 다중 상속 사용
이 참고에서는 Microsoft Foundation 클래스에서 MI(여러 상속)를 사용하는 방법을 설명합니다. MFC에서는 MI를 사용할 필요가 없습니다. MI는 MFC 클래스에서 사용되지 않으며 클래스 라이브러리를 작성할 필요가 없습니다.
다음 하위 항목에서는 MI가 일반적인 MFC 관용구의 사용에 미치는 영향과 MI의 일부 제한 사항을 다루는 방법을 설명합니다. 이러한 제한 사항 중 일부는 일반적인 C++ 제한 사항입니다. 다른 항목은 MFC 아키텍처에 의해 부과됩니다.
이 기술 노트의 끝부분에서 MI를 사용하는 완전한 MFC 애플리케이션을 찾을 수 있습니다.
CRuntimeClass
MFC의 지속성 및 동적 개체 생성 메커니즘은 CRuntimeClass 데이터 구조를 사용하여 클래스를 고유하게 식별합니다. MFC는 이러한 구조 중 하나를 애플리케이션의 각 동적 및/또는 직렬화 가능한 클래스와 연결합니다. 이러한 구조는 애플리케이션이 형식 AFX_CLASSINIT
의 특수 정적 개체를 사용하여 시작할 때 초기화됩니다.
현재 구현에서는 CRuntimeClass
MI 런타임 형식 정보를 지원하지 않습니다. 그렇다고 MFC 애플리케이션에서 MI를 사용할 수 없다는 의미는 아닙니다. 그러나 둘 이상의 기본 클래스가 있는 개체로 작업할 때 특정 책임이 있습니다.
CObject::IsKindOf 메서드는 여러 기본 클래스가 있는 경우 개체의 형식을 올바르게 결정하지 않습니다. 따라서 CObject를 가상 기본 클래스로 사용할 수 없으며 CObject::Serialize 및 CObject::operator new와 같은 멤버 함수에 대한 모든 호출 CObject
에는 범위 한정자가 있어야 C++가 적절한 함수 호출을 구분할 수 있습니다. 프로그램이 MFC 내에서 MI를 사용하는 경우 기본 클래스를 포함하는 CObject
클래스는 기본 클래스 목록에서 가장 왼쪽에 있는 클래스여야 합니다.
대안은 연산자를 사용하는 것입니다 dynamic_cast
. MI를 사용하여 개체를 기본 클래스 중 하나로 캐스팅하면 컴파일러가 제공된 기본 클래스의 함수를 사용하도록 강제 적용됩니다. 자세한 내용은 dynamic_cast Operator를 참조 하세요.
CObject - 모든 클래스의 루트
모든 중요한 클래스는 클래스 CObject
에서 직접 또는 간접적으로 파생됩니다. CObject
에는 멤버 데이터가 없지만 몇 가지 기본 기능이 있습니다. MI를 사용하는 경우 일반적으로 두 개 이상의 CObject
파생 클래스에서 상속됩니다. 다음 예제에서는 클래스가 CFrameWnd 및 CObList에서 상속하는 방법을 보여 줍니다.
class CListWnd : public CFrameWnd, public CObList
{
// ...
};
CListWnd myListWnd;
이 경우 CObject
두 번 포함됩니다. 즉, 메서드 또는 연산자 참조를 명확하게 구분하는 CObject
방법이 필요합니다. 연산자 new 및 operator delete는 명확하게 구분해야 하는 두 개의 연산자입니다. 또 다른 예제로, 다음 코드는 컴파일 시간에 오류를 발생합니다.
myListWnd.Dump(afxDump); // compile time error, CFrameWnd::Dump or CObList::Dump
CObject 메서드 다시 구현
둘 이상의 CObject
파생된 기본 클래스가 있는 새 클래스를 만들 때 다른 사용자가 사용할 메서드를 CObject
다시 구현해야 합니다. 연산 new
자이며 delete
필수이며 덤프 를 사용하는 것이 좋습니다. 다음 예제에서는 및 delete
연산자와 메서드를 다시 구현합니다.Dump
new
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
보여 준 첫 번째 예제에서 가상 메서드는 다른 CFrameWnd
방식으로 구현되므로 여전히 모호합니다 CObList
. 모호성을 제거하는 가장 좋은 방법은 이전 섹션에 제시된 권장 사항을 따르는 것입니다.
CObject::IsKindOf 및 런타임 입력
MFC에서 CObject
지원하는 런타임 입력 메커니즘은 매크로 DECLARE_DYNAMIC, IMPLEMENT_DYNAMIC, DECLARE_DYNCREATE, IMPLEMENT_DYNCREATE, DECLARE_SERIAL 및 IMPLEMENT_SERIAL 사용합니다. 이러한 매크로는 안전한 다운캐스트를 보장하기 위해 런타임 형식 검사 수행할 수 있습니다.
이러한 매크로는 단일 기본 클래스만 지원하며 상속된 클래스를 곱하기 위해 제한된 방식으로 작동합니다. IMPLEMENT_DYNAMIC 또는 IMPLEMENT_SERIAL 지정하는 기본 클래스는 첫 번째(또는 가장 왼쪽) 기본 클래스여야 합니다. 이 배치를 사용하면 가장 왼쪽에 있는 기본 클래스에 대해서만 형식 검사 수행할 수 있습니다. 런타임 형식 시스템은 추가 기본 클래스에 대해 아무것도 알 수 없습니다. 다음 예제에서 런타임 시스템은 검사 형식을 CFrameWnd
수행하지만 이에 대해서는 CObList
알 수 없습니다.
class CListWnd : public CFrameWnd, public CObList
{
DECLARE_DYNAMIC(CListWnd)
// ...
};
IMPLEMENT_DYNAMIC(CListWnd, CFrameWnd)
CWnd 및 메시지 지도
MFC 메시지 맵 시스템이 제대로 작동하려면 다음 두 가지 추가 요구 사항이 있습니다.
파생 기본 클래스는 하나
CWnd
만 있어야 합니다.파생된 기본 클래스는
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를 사용하는 샘플 프로그램
다음 샘플은 CWinApp에서 CFrameWnd
파생된 하나의 클래스로 구성된 독립 실행형 애플리케이션입니다. 이러한 방식으로 애플리케이션을 구조화하지 않는 것이 좋지만, 하나의 클래스가 있는 가장 작은 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;