Compartilhar via


TN016: usando várias heranças do C++ com MFC

Esta observação descreve como usar várias heranças (MI) com Microsoft Foundation Classes. O uso de MI não é obrigatório com MFC. O MI não é usado em nenhuma classe MFC e não é necessário para gravar uma biblioteca de classes.

Os subtópicos a seguir descrevem como o MI afeta o uso de expressões MFC comuns, bem como abrangem algumas das restrições do MI. Algumas dessas restrições são restrições gerais do C++. Outras são impostas pela arquitetura MFC.

Ao final desta nota técnica, você encontrará um aplicativo MFC completo que usa MI.

CRuntimeClass

Os mecanismos de persistência e criação de objeto dinâmico do MFC usam a estrutura de dados CRuntimeClass para identificar exclusivamente classes. O MFC associa uma dessas estruturas a cada classe dinâmica e/ou serializável em seu aplicativo. Essas estruturas são inicializadas quando o aplicativo começa usando um objeto estático especial do tipo AFX_CLASSINIT.

A implementação atual de CRuntimeClass não dá suporte a informações de tipo de runtime do MI. Isso não significa que você não pode usar MI em seu aplicativo MFC. No entanto, você terá certas responsabilidades ao trabalhar com objetos que têm mais de uma classe base.

O método CObject::IsKindOf não determinará corretamente o tipo de um objeto se ele tiver várias classes base. Portanto, você não pode usar o CObject como uma classe base virtual e todas as chamadas para funções membro CObject, como CObject::Serialize e CObject::operator new, devem ter qualificadores de escopo para que o C++ possa desambiguar a chamada de função apropriada. Quando um programa usa MI dentro do MFC, a classe que contém a classe base CObject precisa ser a classe mais à esquerda na lista de classes base.

Uma alternativa é usar o operador dynamic_cast. A conversão de um objeto com MI em uma de suas classes base forçará o compilador a usar as funções na classe base fornecida. Para mais informações, confira Operador dynamic_cast.

CObject – A raiz de todas as classes

Todas as classes significativas derivam direta ou indiretamente da classe CObject. CObject não tem nenhum dado de membro, mas tem alguma funcionalidade padrão. Ao usar o MI, você normalmente herdará de duas ou mais classes derivadas de CObject. O seguinte exemplo ilustra como uma classe pode herdar de um CFrameWnd e um CObList:

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

Nesse caso, CObject é incluído duas vezes. Isso significa que você precisa de uma forma de eliminar a ambiguidade de qualquer referência a métodos ou operadores CObject. O operador new e o operador delete são dois operadores que devem ter a ambiguidade eliminada. Como outro exemplo, o seguinte código causa um erro no tempo de compilação:

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

Como reimplementar métodos CObject

Ao criar uma nova classe que tenha duas ou mais classes base derivadas de CObject, você deve reimplementar os métodos CObject que deseja que outras pessoas usem. Os operadores new e delete são obrigatórios e o Despejo é recomendado. O seguinte exemplo reimplementa os operadores new e delete e o método 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);
    }
    // ...
};

Herança virtual de CObject

Pode parecer que herdar CObject virtualmente resolveria o problema da ambiguidade da função, mas esse não é o caso. Como não há dados de membro em CObject, você não precisa de herança virtual para impedir várias cópias de dados de um membro de classe base. No primeiro exemplo mostrado anteriormente, o método virtual Dump ainda é ambíguo porque é implementado de maneira diferente dentro CFrameWnd e CObList. A melhor maneira de remover a ambiguidade é seguir as recomendações apresentadas na seção anterior.

CObject::IsKindOf e digitação em tempo de execução

O mecanismo de digitação em tempo de execução com suporte do MFC em CObject usa as macros DECLARE_DYNAMIC, IMPLEMENT_DYNAMIC, DECLARE_DYNCREATE, IMPLEMENT_DYNCREATE, DECLARE_SERIAL e IMPLEMENT_SERIAL. Essas macros podem executar uma verificação de tipo de tempo de execução para garantir downcasts seguros.

Essas macros dão suporte a apenas única classe base e funcionarão de maneira limitada para multiplicar classes herdadas. A classe base especificada em IMPLEMENT_DYNAMIC ou IMPLEMENT_SERIAL deve ser a primeira classe base (ou a mais à esquerda). Esse posicionamento permitirá que você faça verificação de tipo somente para a classe base mais à esquerda. O sistema de tipo de tempo de execução não saberá nada sobre classes base adicionais. No exemplo a seguir, os sistemas de tempo de execução farão verificação de tipo em relação a CFrameWnd, mas não saberão nada sobre CObList.

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

CWnd e mapas de mensagens

Para que o sistema de mapa de mensagens MFC funcione corretamente, há dois requisitos adicionais:

  • Deve haver apenas uma classe base derivada de CWnd.

  • A classe base derivada de CWnd deve ser a primeira classe base (ou a mais à esquerda).

Aqui estão alguns exemplos que não funcionarão:

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

Um programa de exemplo usando MI

O exemplo a seguir é um aplicativo autônomo que consiste em uma classe derivada de CFrameWnd e CWinApp. Não recomendamos que você estruture um aplicativo dessa maneira, mas este é um exemplo do menor aplicativo MFC que tem uma 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;

Confira também

Observações técnicas por número
Observações técnicas por categoria