Dúvida com Headers e Implementações em C++

Oi pessoal.
Estou estudando C++ a um tempo pelo site cplusplus.com. Eu estou achando bastante interessante, porém acho que eles emitem grande parte de Orientação a Objetos. Quando terminar de estudar o que falta (Templates, Namespace e mais uns 3 tópicos), quero estudar o Accelerated C++ e o Effective C++. Vocês acham uma boa idéia?

Eu dei uma procurada (bem pouco, na verdade) e não achei uma resposta direta para o meu problema.
Bom, o meu problema é que eu tenho 2 headers: poligono.h e triangulo.h.

// poligono.h
class Poligono
{
	private:
		int altura, largura;
	public:
		Poligono();
		~Poligono();
		void setValores(int, int);
};
// triangulo.h
#include <iostream>
#include "poligono.h"

using namespace std;

class Triangulo : public Poligono
{
	public:
		Triangulo();
		~Triangulo();
		void mostrarDescricao();
};

Eu tenho o include “poligono.h” nesse último código por causa da herança. Está correto isso?

Tenho também 3 arquivos: poligono.cpp, triangulo.cpp e main.cpp. Em cada .cpp eu tenho o include do respectivo header.

// poligono.cpp
#include <iostream>
#include "poligono.h"

using namespace std;

Poligono::Poligono()
{
	cout << endl << "Criando um poligono." << endl << endl;
}

Poligono::~Poligono()
{
	cout << endl << "Destruindo um poligono." << endl << endl;
}

void Poligono::setValores(int novaAltura, int novaLargura)
{
	altura = novaAltura;
	largura = novaLargura;
}
// triangulo.cpp
#include <iostream>
#include "triangulo.h"

using namespace std;

Triangulo::Triangulo()
{
	cout << endl << "Criando um triangulo." << endl << endl;
}

Triangulo::~Triangulo()
{
	cout << endl << "Destruindo um triangulo." << endl << endl;
}

void Triangulo::mostrarDescricao()
{
	cout << endl << "I'm a triangle!" << endl << endl;
}
// main.cpp
#include <iostream>
#include "poligono.h"
#include "triangulo.h"

using namespace std;

int main()
{
	Poligono p;
	return 0;
}

Bom, estou usando Linux e pra compilar, primeiramente eu gero os objetos:

[quote]dede@dede-laptop:~/Área de Trabalho/teste$ g++ -c poligono.cpp
dede@dede-laptop:~/Área de Trabalho/teste$ g++ -c triangulo.cpp
[/quote]

E depois, tento compilar a main.cpp:

[quote]dede@dede-laptop:~/Área de Trabalho/teste$ g++ -o main main.cpp
[/quote]

E tenho o seguinte erro:

[quote]In file included from triangulo.h:2,
from main.cpp:3:
poligono.h:1: erro: redefinition of ?class Poligono?
poligono.h:2: erro: previous definition of ?class Poligono?[/quote]

Bom, já tentei mudar algumas coisas e não sei o que pode estar errado. Deve ser uma coisa bem boba que eu estou esquecendo de fazer.
Alguém pode me dar uma ajuda?

Abraço.

[editado]
Recentemente, fui parar no FAQ do parashift.com e li que é aconselhável sempre escrever “std::cout”. Vocês concordam com isso? Isso não demanda muito tempo de codificação?
Abraço.
[/editado]

Faltou definir a expressão #ifndef #define #endif em seus arquivos de extensão h. Assim:

#ifndef _POLIGONO_H_
#define _POLIGONO_H_

// poligono.h  
class Poligono  
{  
    private:  
        int altura, largura;  
    public:  
        Poligono();  
        ~Poligono();  
        void setValores(int, int);  
};

#endif

e assim:

[code]#ifndef TRIANGULO_H
#define TRIANGULO_H

// triangulo.h
#include
#include “poligono.h”

using namespace std;

class Triangulo : public Poligono
{
public:
Triangulo();
~Triangulo();
void mostrarDescricao();
};

#endif[/code]

O que acontece é que os includes são meio burros, o include realmente significa “ponha o que estiver escrito neste arquivo aqui”, mesmo fazendo isso duas vezes.
Olha só o seu main, primeiro dá um include em “poligono.h”, beleza. Depois, dá um include em “triangulo.h”, e este dá include em “poligono.h”, pela segunda vez. Sem tratamento, ocorre duas declarações da classe Poligono em seu código.

A expressão ifndef, define e endif são macros que servem para evitar isso. Vamos à explicação de cada um deles:
:arrow: #ifndef MACRO: indica que, se não estiver definido a variável MACRO, então insira o trecho para a compilação até o próximo #endif
:arrow: #define MACRO: cria uma macro chamada MACRO sem nenhum valor

Assim, a expressão #ifndef é avaliada como true na primeira vez que passa por ele. Nas próximas vezes, por ter sido definido uma macro internamente, será avaliada como false e nunca será avaliada novamente.

Leonardo,

Muito obrigado por responder. Eu tinha ouvido falar que tinha a ver com as diretivas.
Porém, continua dando o mesmo erro. Será que eu preciso mudar alguma coisa nos .cpp ou na main também? E eu estou compilando da maneira correta (gerando os arquivos objetos antes)?

Abraço!

[editado]
Os fontes poligono.h e triangulo.h estão da seguinte maneira (respectivamente):

[code]
#ifndef TRIANGULO_H
#define TRIANGULO_H

#include “poligono.h”

using namespace std;

class Triangulo : public Poligono
{
public:
Triangulo();
~Triangulo();
void mostrarDescricao();
};

#endif[/code]

#ifndef _POLIGONO_H_
#define _POLIGONO_H_

class Poligono
{
	private:
		int altura, largura;
	public:
		Poligono();
		~Poligono();
		void setValores(int, int);
};

#endif

Tem alguma coisa de errado com eles? Fiz exatamente como você falou, Leonardo.
Nos .cpp de cada um eu só uso o include “poligono.h” ou “triangulo.h”.

Eu estou fazendo correto em gerar o “.o” e depois compilar a main?

Abraço.
[/editado]

Estranho. Eu peguei os fontes (veja se estão OK) e compilaram direitinho (observação: cuidado com a última linha. Para evitar problemas sempre termine a última linha com uma quebra de linha, para evitar alguma coisa esquisita do compilador).

#ifndef _POLIGONO_H_
#define _POLIGONO_H_
 // poligono.h  
 class Poligono  
 {  
     private:  
         int altura, largura;  
     public:  
         Poligono();  
         ~Poligono();  
         void setValores(int, int);  
 }; 
#endif 
#ifndef _TRIANGULO_H_
#define _TRIANGULO_H_
 // triangulo.h  
 #include &lt;iostream&gt;  
 #include "poligono.h"  
   
 using namespace std;  
   
 class Triangulo : public Poligono  
 {  
     public:  
         Triangulo();  
         ~Triangulo();  
         void mostrarDescricao();  
 };
#endif

Eu normalmente usaria uma makefile para compilar, mas como são poucos arquivos, é mais fácil compilar todo mundo de uma vez em um só comando:

g++ -o teste main.cpp triangulo.cpp poligono.cpp

Se você precisar compilar um grande projeto, você pode compilar um de cada vez, mas nesse caso é melhor usar um makefile.

Uma coisa que você deve notar é que um destrutor deve SEMPRE ser declarado como virtual - isso vai lhe salvar a vida mais tarde.

Vou dar um exemplo de código comentado com Doxygen, que é uma prática que você deve usar SEMPRE em C++.

#ifndef _POLIGONO_H_
#define _POLIGONO_H_
// poligono.h  

// Atenção: o recomendado, em C++, é usar a documentação estilo Doxygen ou XML Documentation.
// Se for usar o Doxygen, você pode usar o estilo nativo (que é o que vou mostrar) ou o estilo Javadoc
// (também aceito pelo Doxygen).
// Olhe o site www.doxygen.org e veja como é que se deve fazer.
// Outra dica: deixe as partes públicas antes das partes protegidas ou privadas. Veja abaixo.
// Em declarações de parâmetros, é interessante pôr sempre os nomes, mesmo que não sejam necessários
// em protótipos, até para ajudar a sua IDE preferida.
// Não se esqueça:
// a) Destrutores devem ser sempre virtuais, a menos que você saiba o que está fazendo.
// b) Se você definir um construtor default, defina também um "copy constructor".

/**
 * \class Poligono
 *
 * \brief Representa um polígono.
 */
class Poligono  
{  
public:
    /**
     * \brief Atribui novos valores à altura e à largura.
     * \param novaAltura O novo valor de altura.
     * \param novaLargura O novo valor de largura.
     */
    void setValores(int novaAltura, int novaLargura);  
public:  
    /**
     * \brief Construtor default.
     */
    Poligono();  
    /**
     * \brief Copy constructor.
     */
    Poligono (const Poligono& outro);
    /**
     * \brief Destrutor.
     */
    virtual ~Poligono();  
private:  
    /**
     * \brief Altura em pixels.
     */
    int altura;
    /**
     * \brief Largura em pixels.
     */
    int largura;  
}; 
#endif 

thingol,

Muito obrigado pela sua resposta. Funcionou… Eu acho que estava dando algum erro porque depois de compilar o poligono.cpp e o triangulo.cpp eu compilava a main.cpp de maneira normal

g++ -o main main.cpp E tinhas esses erros:

[quote]dede@dede-laptop:~/teste$ g++ -o main main.cpp
/tmp/ccgVk5CB.o: In function main': main.cpp: (.text+0x195) : undefined reference toPoligono:: Poligono()'
main.cpp: (.text+0x1a5) : undefined reference to `Poligono::~Poligono()'
collect2: ld returned 1 exit status[/quote]
Mas compilando tudo de uma vez funcionou perfeitamente! Muito obrigado! :slight_smile:

Sobre o makefile, algumas pessoas me falaram sobre isso (acho que foi até aqui no GUJ). Me alertaram sobre preferir usar uma IDE que faz automaticamente o makefile. É o mais indicado?

Me surgiu uma dúvida.
Você falou em sempre declarar o destrutor como virtual. Isso serve pra que? Pra que a base class também seja destruída depois que eu instanciar uma subclasse? (por questões de memória ou coisa do tipo?). Mas mesmo eu não deixando ele virtual, ele (o destrutor da base class) também é chamado quando eu uso o delete em uma subclasse, certo?

Abraço e obrigado mais uma vez!

a) Quando você tem muitos fontes em um projeto, manter a makefile é muito chato. É melhor usar uma IDE mesmo.

b)

Hum… uma coisa que aprendi em C++ é que há muitas coisas que você tem de adotar como “melhores práticas” (como o caso de sempre deixar o destrutor como virtual), já que entender EXATAMENTE o que ocorre nem sempre é muito trivial.
Confesso que não sei o que ocorre exatamente se você não deixar o destrutor como virtual - precisaria escrever um programa de teste para ver se ele é chamado corretamente ou não.

Acho que você não entendeu como é que funciona a parte de compilação e de linking do gcc e de outros compiladores C/C++.

Quando você quer criar um executável, você precisa mencionar os arquivos .o e os arquivos .a na sua linha de comando explicitamente; não é como no Java, em que basta mencionar um diretório e o Java sai “caçando” os arquivos. No seu caso, você poderia ter feito algo como:

# Isto cria o arquivo "poligono.o"
g++ -c poligono.cpp

# Isto cria o arquivo "triangulo.o"
g++ -c triangulo.cpp

# Isto cria o arquivo "main.o"
g++ -c main.cpp

# Isto cria o arquivo executável "main" a partir de main.o, triangulo.o e poligono.o
g++ -o main main.o triangulo.o poligono.o

Quando eu mandei compilar tudo de uma vez, é como se fosse um atalho para a seqüência de operações que postei acima.

thingol,

O que faltou quando eu fiz os .o’s é que eu não colocava eles junto pra fazer o executável. Obrigado por solucionar essa dúvida.

Como você pode ver, os destrutores das classes não são virtuais.
Quando eu cria um triângulo, ele chama o construtor de Poligono e de Triangulo. Quando o programa acaba, ele chama o destrutor de Triangulo e depois do Poligono.

O código da main:

[code]// main.cpp
#include
#include “poligono.h”
#include “triangulo.h”

using namespace std;

int main()
{
Triangulo t;
return 0;
}[/code]

A resposta, sem usar virtual no destrutor é essa:

[quote]Criando um poligono.

Criando um triangulo.

Destruindo um triangulo.

Destruindo um poligono.[/quote]

Colocando virtual no destrutor de Poligono e Triangulo, a saída foi a mesma…
É só mudar no header, né? Não preciso alterar nada na implementação (nos .cpps)?

Abraço!

É.

Na verdade, você precisa olhar o livro “C++ Faqs” (Marshall Cline) ou então olhar o site C++ FAQ LITE para entender algumas das inúmeras sutilezas da linguagem C++.

Se você quiser aprender direitinho C++ é interessante dar uma olhada.

No caso de destrutores e por que eles têm de ser virtuais, veja:

20.7. When should my destructor be virtual?

Ou seja, se seu programa tem algum uso da seguinte construção:

Poligono *p = new Triangulo();
delete p;

que é semelhante à construção Java:

Poligono p = new Triangulo();

as classes TÊM de ter destrutores virtuais. Como isso é esperado em um programa que siga as diretrizes da Programação Orientada a Objetos, então você TEM de tornar todos os seus destrutores virtuais, sob pena de chamar o destrutor errado.

Ele tem de