quarta-feira, 29 de abril de 2009

Include circular

Outro dia um colega tava tendo dificuldades com a inclusão circular num programa escrito em c++. Como esta pode ser uma dúvida frequente entre programadores iniciantes na linguagem, vou tentar esclarecê-la. xD

o problema da inclusão circular acontece quando precisamos incluir um arquivo cabeçalho dentro de outro. Como em:


//Classe B usa a classe A
#ifndef _B_
#define _B_
#include "classeA.h"

class B{
private:
A a;
public:
};

#endif

//Classe A usa a classe B
#ifndef _A_
#define _A_
#incude "classeB.h"

class A{
private:
B b;
public:
};
#endif

//Classe C usa a classa A e a classe B
#ifndef _C_
#define _C_
#include "classeB.h"
#include "classeA.h"

class C{
private:
A a;
B b;
public:
};

#endif




Mesmo colocando diretivas esse código não deve compilar. A diretiva #ifndef ALGO serve para que, caso ainda não tenha sido definido o ALGO entao o compilador deve 'ler' o restante caso contrário deve ignorar. Neste caso usamos essa diretiva para que o compilador nao inclua o código milhares de vezes, e sim apenas uma única vez. Então, caso ainda não tenha sido definido esse algo ele deve definir( #define ALGO) e compilar todo o código até o #endif (e seguir normalmente) e caso alguém tente usar esse código, e o compilador tente compilar novamente ele não consiguirá, porque já está definido o ALGO. Portanto o compilador deve ter compilado esse código apenas uma vez \o/

Então por que geraria a inclusão circular e não compilaria? Justamente por causa dessas diretivas. Então por que não tirá-las? Porque caso o faça, o compilador pode incluir o mesmo código indefinidas vezes causando diversos erros de compilação. Vamos supor que o compilador queira compilar a classe B primeiro. Oras, a classe B inclui a classe A, mas a classe A inclui a classe B, a qual ja foi definida, por tanto o compilador nao pode compila-la. Entao faltará a classe B para a classe A.

Exemplificando:

//Classe B usa a classe A
#ifndef _B_
#define _B_

#include "classeA.h"

#ifndef _A_
#define _A_
/*
aqui a classe B ainda não foi declarada, mas a classe A abaixo irá tentar usá-la.
*/
#incude "classeB.h"
/*o comilador aqui não pode fazer esse include,
porque há uma diretiva que não permite
portnato a classe A não pode usar a classe B*/


class A{
private:
B b;
public:
};
#endif


class B{
private:
A a;
public:
};

#endif


Qual a solução? Simples. Uma forward declaration, ou seja uma declaração antecipada. Ao invés de dizer que inclui uma classe toda (ou seja o arquivo .h ou .hh), a qual acabaria gerando uma inclusão circular, diremos apenas que há uma classe chamada 'X'. Como consequencia desse método é que devemos, usar referências, ao invés de variáveis, justamente por apenas sabermos que existe um classe, mas não como ela é definida.

solução:
//Classe B usa a classe A
#ifndef _B_
#define _B_

class A; //uma declaração antecipada.

class B{
private:
A* a; //aqui deve ser um ponteiro, porque não
//temos definido a classe A

public:
};

#endif

//Classe A usa a classe B
#ifndef _A_
#define _A_

class B; //uma declaração antecipada.


class A{
private:
B* b;//idem
public:
};
#endif

//Classe C usa a classa A e a classe B
#ifndef _C_
#define _C_

#include "classeB.h"
#include "classeA.h"

/*
como aqui, a inclusão das classes não
deve gerar a inclusão circular, podemos deixar assim.
*/


class C{
private:
A a;
B b;
public:
};

#endif

Bom, como iremos implementar esse código em algum arquivo, e precisaremos saber como são descritas as classes, devemos incluir o arquivo cabeçalho no arquivo de implementação.

Termos, então, seguindo o exemplo, tres arquivos cabeçalhos: classeA.h, classeB.h e classeC.h
e por consequencia teremos tres arquivos de implementação, que devem incluir o cabeçalho da classe declarada antecipadamente. Por exemplo

o arquivo de implementação da classe classeB.h, poderia ser:

#include "classeB.h"
#include "classeA.h"

B::metodos(){} //etc

o arquivo de implementação da classe classeA.h, poderia ser:

#include "classeA.h"
#include "classeB.h"

A::metodos(){} //etc

e o da classe classeC.h, poderia ser:

#include "classeC.h"

C::metodos(){}


Bom comentei uma solução para o problema exposto, a que na minha opnião é melhor. Dúvidas e sugestões são bem vindas xD

Nenhum comentário:

Postar um comentário