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 Carro

Para exemplificar, utilizaremos a classe Carro trabalhada anteriormente. Primeiro, escrevemos a declaração da classe Carro no arquivo Carro.h:

class Carro {

    public:
        Carro();

        bool abastecer(float litros);
        bool mover(float km);

        float getTanque();
        float getDistancia();

    private:
        float tanque;
        float distancia;

};

Declaração da classe Carro (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 CARRO_H
#define CARRO_H

class Carro {

    public:
        Carro();
	...
};
#endif

Implementação da classe Carro

A seguir, escrevemos o arquivo Carro.cpp, que contém a implementação dos métodos da classe Carro.

#include "Carro.h"

Carro::Carro() {
    tanque = 0;
    distancia = 0;
}

bool Carro::abastecer(float litros) {
    tanque += litros; // tanque = tanque + litros;
    ...
}

...

Programa Principal

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

#include <iostream>
#include "Carro.h"

using namespace std;

int main()
{
    Carro carro1, carro2;

    carro1.abastecer(20);
    carro2.abastecer(30);

    bool moveu1 = carro1.mover(200);
    if(moveu1==false)
        cout << "Carro 1: Não há combustível suficiente!" << 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: main

main: main.o carro.o
	g++ -o main main.o carro.o

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

carro.o: carro.cpp
	g++ -c carro.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: todos warnings e debug info

PROG = main
FONTES = main.cpp carro.cpp
OBJETOS = $(FONTES:.cpp=.o)

$(PROG): $(OBJETOS)
	g++ $(CXXFLAGS) $(OBJETOS) -o $@

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

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

main.o: carro.h
carro.o: carro.h

Compilação Modular: Alternativas ao make

Já existem alguns sistemas alternativos ao make:

Exercícios

Exercícios (2)