FACIN logo

Modularização de Programas em C++

Marcelo Cohen, FACIN - PUCRS

Motivação: Por que modularizar ?

Solução: Modularização

Agrupam-se as classes relacionadas em módulos. Para cada módulo são utilizados dois arquivos:

Para usar uma classe em outro módulo, basta então incluir o arquivo .h correspondente

Declaração da classe Data

Para exemplificar, utilizaremos o mesmo exemplo da composição: um produto com data de validade. Primeiro, escrevemos a declaração da classe Data no arquivo Data.h:

#include <string>

class Data {
  public:
	Data(int umDia=1,int umMes=1, int umAno=2000);
	void set(int umDia, int umMes, int umAno);
	void get(int& umDia, int& umMes, int& umAno);
	std::string toString(void);
	std::string extenso(void);
  private:  
	int dia,mes,ano;
	static const std::string meses[];
};

Declaração da classe Data (2)

Porém, se por engano esse arquivo for incluído mais de uma vez, haverá uma duplicidade de todas as definições. Como isso pode acontecer facilmente, acrescentamos 3 linhas no arquivo .h:

#ifndef DATA_H
#define DATA_H
#include <string>

class Data {
  public:
	Data(int umDia=1,int umMes=1, int umAno=2000);
	...
};
#endif

Implementação da classe Data

A seguir, escrevemos o arquivo Data.cpp, que contém a implementação dos métodos da classe Data (e a definição da constante meses).

#include <iostream>
#include <iomanip>
#include <sstream>
#include "Data.h"
using namespace std;

const string Data::meses[] = {
	"janeiro", "fevereiro", "março", "abril", ...
};

Data::Data(int umDia,int umMes, int umAno) {
	set(umDia,umMes,umAno);
}
...

Declaraçao da classe Produto

A classe Produto deve ser declarada de forma semelhante, no arquivo Produto.h:

#ifndef PRODUTO_H
#define PRODUTO_H

#include "Data.h"

class Produto {
  public:
	Produto(int cod,std::string descr,const Data& val);
	int getCodigo() { return codigo; }
	std::string getDescricao() { return descricao; }
	Data& getData() { return validade; }
	std::string toString(void);
  private:
	int codigo;
	std::string descricao;
	Data validade;
};
#endif

Implementação da classe Produto

A classe Produto deve então ser implementada no arquivo Produto.cpp:

#include <iostream>
#include <sstream>
#include "Produto.h"
using namespace std;

Produto::Produto(int cod,string descr,const Data& val) {
	codigo = cod;
	descricao = descr;
	validade = val;
}
...

Programa Principal

Finalmente, o programa principal deve ser implementado em um arquivo separado, como por exemplo testadataproduto.cpp:

#include <iostream>
#include "Data.h"
#include "Produto.h"

using namespace std;

int main()
{
	Data d1(30,10,2006);
	cout << d1.toString() << endl;
	cout << d1.extenso() << endl;

	Produto p1(1005,"Leite longa vida",d1);
	cout << p1.toString() << endl;
}

Compilação dos Módulos

Para compilar o programa completo, é preciso indicar todos os fontes na linha de comando:

Porém, essa estratégia tem algumas implicacões:

Para um programa tão pequeno, isso não faz tanta diferença...

Mas se o programa for composto por muitos módulos, compilá-los separadamente pode ter um custo considerável, além de não ser muito prático.

Compilação Modular: Makefiles

Um Makefile é um roteiro de compilação:

O nome do arquivo deve ser preferencialmente Makefile.
Para utilizá-lo, basta digitar o comando make no terminal.

all: testa

testa: testadataproduto.o Produto.o Data.o
	g++ -o testa testadataproduto.o Produto.o Data.o

testadataproduto.o: testadataproduto.cpp
	g++ -c testadataproduto.cpp

Produto.o: Produto.cpp
	g++ -c Produto.cpp

Data.o: Data.cpp
	g++ -c Data.cpp

Compilação Modular: Makefiles (2)

Os comandos de compilação não parecem meio repetitivos ? Podemos melhorar o Makefile, criando regras genéricas:

CXXFLAGS = -Wall -g    # Opções do compilador

FONTES = testadataproduto.cpp Produto.cpp Data.cpp
OBJETOS = $(FONTES:.cpp=.o)

all: testa

testa: $(OBJETOS)
	g++ $(CXXFLAGS) $(OBJETOS) -o testa

clean:
	-@ rm -f $(OBJETOS)

Compilação Modular: Makefiles (3)

Mas e como especificar dependências automáticas para os .h ?
Uma forma é incluir uma regra que use o comando makedepend:

...
clean:
	-@ rm -f $(OBJETOS)

depend:
	makedepend -- ${CFLAGS} -- ${FONTES}

Ao digitarmos make depend, o comando makedepend será ativado, e verificará as dependências de cada módulo, incluindo estas no final do próprio Makefile:

...
depend:
	makedepend -- ${CFLAGS} -- ${FONTES}

# DO NOT DELETE

testadataproduto.o: Data.h Produto.h
Produto.o: Produto.h Data.h
Data.o: Data.h

Compilação Modular: Alternativas ao make

Já existem alguns sistemas alternativos ao make:

Exercícios