Modularização
❤️ Arquivos .h (ou .hpp)
Lembrando do nosso objetivo
- Com TADs queremos que o resto do programa seja cliente
- Apenas use as operações do mesmo.

Antes de tudo
Vamos entender um pouco como organizar um código
- Todos os nossos arquivos
.cppvão ficar na pastasrcsrcé source ou fontes: aqui moram os arquivos fontes.
- Vamos criar arquivos
.hpara definir o contrato do TAD - Os arquivos
.hmoram na pastainclude(inclua isso) - Por um
Makefilevai compilar tudo (vamos falar dele depois!)
. project
├── Makefile
├── include
│ └── ponto.h
│ └── retangulo.h
└── src
│ └── ponto.cpp
│ └── retangulo.cpp
E estes arquivos .h?
Lembre-se que TADs são contratos. Os arquivos .h é a forma que C/C++ tem de separar o contrato da implementação.
Observe o exemplo de ponto.h abaixo:
#ifndef PDS2_PONTO_H
#define PDS2_PONTO_H
/*
* Representa um ponto em duas dimensões. Não faz muito
* mais do que isso :-)
*/
class Ponto {
private:
double _x;
double _y;
public:
/*
* @brief Constutor do nosso ponto.
*/
Public(double x, double y);
/*
* @brief Retorna o valor na coordenada x
*/
double get_x();
/*
* @brief Retorna o valor na coordenada y
*/
double get_y();
};
#endif
Vamos por partes
Include Guards
Observe que o arquivo começa e termina com:
#ifndef PDS2_PONTO_H
#define PDS2_PONTO_H
// ...
#endif
Essas três chamadas são conhecidas como “include guards”. Tais linhas evitam bugs (ver aqui na hora de fazermos uso dos nossos arquivos .h.
Definições
Em segundo lugar, observe que temos só o contrato
/*
* Representa um ponto em duas dimensões. Não faz muito
* mais do que isso :-)
*/
class Ponto {
private:
double _x;
double _y;
public:
/*
* @brief Constutor do nosso ponto.
*/
Public(double x, double y);
/*
* @brief Retorna o valor na coordenada x
*/
double get_x();
/*
* @brief Retorna o valor na coordenada y
*/
double get_y();
};
- Observe que todos os métodos não tem comportamento nenhum.
- Além disso, todos estão documentados. Não é obrigatório mas é uma boa prática.
- Assim qualquer pessoa que vai usar nosso código consegue entender quais são os contratos!
Vamos fazer o outro contrato
O módulo retângulo é cliente do módulo ponto
- Observe como o código abaixo inclui o
ponto.h - Quando um include é
#include <assim>- Estamos incluindo uma biblioteca do sistema
- Quando é
#include "assim"- Estamos incluindo um arquivo nosso
O módulo retângulo
#include "../include/ponto.h"
/*
* Um Retângulo pode ser representado com um
* ponto de origem, uma altura e uma largura.
*
* largura
* ---------------
* | |
* | | altura
* | |
* ---------------
* (x,y)
* origem
*/
class Retangulo {
private:
double _x;
double _y;
public:
/*
* @brief Constutor do nosso ponto.
*/
Retangulo(Ponto _origem, double altura, double largura);
/*
* @brief Pega a área do retângulo
*/
double get_area();
/*
* Testa se dois retângulos tem alguma interseção.
* parte da premissa que altura e larguras só podem
* ser positivas!
*
* @brief Retorna true se este retângulo tem intersção com
* o outro
*/
bool interseccao(Retangulo outro);
};
Caminhos relativos
Sobre o ../include/ponto.h
Vai parece redundante, mas vai ficar mais claro quando fizer o .cpp
- Para entender a linha
#include "../include/ponto.h" - Precisamos entender de caminhos relativos
- .. é um caminho para a pasta superior à
include - Ou seja, a pasta raiz do projeto
- Dentro dela, naturalmente, existe a pasta
include - Se eu saio de uma pasta, e vou para a logo a acima a pasta e onde saí existe na logo acima
- Dentro de include existe o arquivo
ponto.h - Veja a figura abaixo.

- Antes de ver o código vamos entender o motivo pelo qual estamos fazendo essa nova organização.
“Agrupar para conquistar”
Juntar elementos inter-relacionados
- Manutenção, compreensão, …
- Aspectos da funcionalidade do programa
- Programação modular
- Partes independentes e intercambiáveis
- Quebrar em módulos permite desenvolver um código mais fácil de manter.
Boas Práticas
O Módulo
- Tem um propósito único
- Interface apropriada com outros módulos
- Pode ser compilado separadamente
- Reutilizáveis e Modificáveis
- Geralmente, um único TAD ou um TAD com
structseenumsque são apoio ao TAD
Exemplo 1
Observe como tenho os enums abaixo que serão utilizados pelo TAD (além de clientes do TAD)
Arquivo .h
#ifndef PDS2_CARTA_H
#define PDS2_CARTA_H
enum num {
AS, N2, N3, N4, N5, N6, N7, N8, N9, N10, J, Q, K
};
enum naipe {
COPAS, ESPADAS, OURO, PAUS
};
/*
* A classe carta cuida de representar
* uma carta do baralho padrão. Fazemos
* uso de enums para garantir que nunca
* seja inválida.
*/
class Carta {
private:
num _numero;
naipe _naipe;
public:
/*
* @brief Constrói uma carta
*/
Carta(num numero, naipe naipe);
/*
* @brief Pega o número
*/
num get_numero();
/*
* @brief Pega o naipe
*/
naipe get_naipe();
};
#endif
Exemplo 2
#ifndef PDS2_PESSOA_H
#define PDS2_PESSOA_H
#include <string>
struct endereco_t {
std::string rua,
std::string bairro,
std::string cidade
};
/*
* A classe pessoa serve para associarmos
* nomes aos endereços.
*/
class Pessoa {
private:
endereco_t _endereco;
std::string _nome;
public:
/*
* @brief Constrói uma pessoa
*/
Pessoa(std::string nome, std::string pessoa);
// ... Resto da classe aqui
};
#endif
Ok, mas e o código?
- O código vai morar na pasta
Arquivos .cpp
- Os arquivos .cpp vão guardar a implementação do contrato
- Os mesmos moram na pasta
src - Antes de criar os mesmos temos que fazer o include do
.hque criamos antes.
Exemplo 1
Arquivo .h
/*
* Representa um ponto em duas dimensões. Não faz muito
* mais do que isso :-)
*/
class Ponto {
private:
double _x;
double _y;
public:
/*
* @brief Constutor do nosso ponto.
*/
Public(double x, double y);
/*
* @brief Retorna o valor na coordenada x
*/
double get_x();
/*
* @brief Retorna o valor na coordenada y
*/
double get_y();
};
Arquivo .cpp
#include "../include/ponto.h"
Ponto::Ponto(double x, double y) {
_x = x;
_y = y;
}
double Ponto::get_x() {
return _x;
}
double Ponto::get_y() {
return _y;
}
Como que funciona?
O include
- A primeira linha é:
#include "../include/ponto.h" - Caminhe até a pasta acima de source com .. (já falamos disso ainda agora)
- Na pasta mãe de
srcexiste uma pastainclude - Lá existe o arquivo
Preste atenção em alguns detalhes
- O arquivo
.cppé um pouco diferente das outras formas que declaramos métodos e funções - Porém lembre-se, em C++,
::significa pertence
// retorno Classe::nome
double Ponto::get_x()
- A linha acima fala que: na classe Ponto, existe um método
get_xque retorna um double. - Depois de dizer isso você diz: tá aqui o códgo do método
double Ponto::get_x() {
return _x
}
E o construtor?
- Na classe Ponto existe um construtor que não retorna nada
- Afinal, é um construtor
- Tá aqui como ele é implementado
Ponto::Ponto(double x, double y) {
_x = x;
_y = y;
}
Mais um exemplo
Exemplo 2
Arquivo .h
#ifndef PDS2_CARTA_H
#define PDS2_CARTA_H
enum num {
AS, N2, N3, N4, N5, N6, N7, N8, N9, N10, J, Q, K
};
enum naipe {
COPAS, ESPADAS, OURO, PAUS
};
/*
* A classe carta cuida de representar
* uma carta do baralho padrão. Fazemos
* uso de enums para garantir que nunca
* seja inválida.
*/
class Carta {
private:
num _numero;
naipe _naipe;
public:
/*
* @brief Constrói uma carta
*/
Carta(num numero, naipe naipe);
/*
* @brief Pega o número
*/
num get_numero();
/*
* @brief Pega o naipe
*/
naipe get_naipe();
};
#endif
Arquivo .cpp
#include "../include/carta.h" // Vou implementar esse contrato
Carta::Carta( // Na classe Carta tem um construtor
num numero, naipe naipe
) {
_numero = numero;
_naipe = naipe;
}
num Carta::get_numero() { // Na classe Carta tem um `get_numero` que retorna um num
return _numero;
}
naipe Carta::get_naipe() {
return _naipe;
}
- Vamos agora falar um pouco das vantagens antes de entrar em mais exemplos
Projeto Modular
Propriedades
- Decomposição
- Composição
- Significado fechado
- Continuidade
- Proteção
Decomposição
- Nível de Projeto
- Capaz de separar uma tarefa em subtarefas, que podem ser abordadas separadamente
- Nível de Software
- Capaz de trabalhar em cada um dos módulos do software independente do outros módulos
- O que pode prejudicar a decomposição?
Composição
- Capacidade de conseguir combinar de forma livre diferentes elementos de software

Continuidade
- Alterações em parte da especificação demandam alterações em poucos módulos
- Bom exemplo
- Utilização de constantes
- Mau exemplo
- Dependência forte de um único módulo
Proteção
- Situações anormais em tempo de execução não são propagadas para outros módulos
- Erros não detectados em outras partes
- Extensibilidade
- Validação dos dados nos módulos
- Tipos, asserções, exceções
Compilação
- Grandes sistemas
- Equipes de programadores
- Código distribuído em vários arquivos fonte
Não é conveniente recompilar partes (todo) do programa que não foram alteradas
- Princípio do encapsulamento
- Separar a especificação de como a classe é usada dos detalhes de como é implementada
Um exemplo maior
Como compilar o código?
g++ src/ponto.cpp src/retangulo.cpp main.cpp -o main
- Passamos TODOS os arquivos do nosso código para o compilador
- O compilador cuida de compilar cada parte separada
Exemplos das Cartas no GitHub
- Na pasta abaixo temos um exemplo maior com um arquivo main
- Exemplo
- Para compilar o mesmo devemos fazer
g++ src/jogador.cpp src/carta.cpp src/baralho.cpp main.cpp -o main
Compilacao
- Na prática o compilador segue o esquema abaixo
- Cada
.cppé compilado separadamente - Depois colamos todos com um
linker - Tudo é oculto de você como programador

Uma visão abstrata do processo
- Cada passo gera código de máquina
- O linker cola tudo junto
- Os arquivos
.hajudam a compilar os.cppindividualmente - O
retangulo.cpppode ser compilado sem oponto.cpp- Qual o motivo? O
retangulo.cppsabe do contrato daponto.h - Então eu posso compilar mesmo sem ter o outro pronto
- Qual o motivo? O
- Depois o linker cola tudo junto

Precisa de .h e .cpp?
Sobre o processo de compilação
- Compilar código é um processo custoso
- Aqui, por custoso leia-se,lento e que demanda muito uso de CPU (e leitura do disco)
- Compilar partes separadas nos permite realizar o processo em paralelo
- Algo que não fazemos aqui, mas ok
Sobre módulos
- Usando
gcc -cvocê pode compilar seu módulo e re-utilizar o mesmo - Se o seu módulo usa
<string>, não tem motivo para compilar o módulo<string> - Sempre que você usa
gcc -cvocê gera um arquivo.o. Seu computador é cheio de tais arquivos.- Ou de arquivos
.so,.a,.dlletc. - São módulos pré-compilados
- Ou de arquivos
- Cada módulo até sabe de outros (através arquivos
.h) mas foi compilado isoladamente.
Como automatizar o processo de compilação?
Ideia (Sistemas de Build/Construção)
- Compilar código “na mão” é um processo tedioso
- Existem ferramentas para automatizar esse processo
- Pense em um script, um pequeno código de roteiro, que diz: estes são os passos para compular este código
Ferramentas
- Sistemas de build, ou seja de construção, são comuns no processo de desenvolvimento de software
- “People love to hate build systems.” ([ref])[https://cliutils.gitlab.io/modern-cmake/]
- A grande verdade é que não existe uma ferramenta única para este propósito
Sistema Make
- No nosso curso vamos fazer uso do sistema make
- Caso você use um sistema Linux, como o WSL, já deve ter o make
- Se usa Windows, veja esta pergunta aqui
- O comando básico é
make - O comando depende de um arquivo chamado de
Makefile
Makefile
- Arquivo de texto especialmente formatado para um programa Unix chamado
make - Contém uma lista de requisitos para que um programa seja considerado ‘up to date’
- O programa make examina esses requisitos
- verifica os timestamps em todos os arquivos de origem;
- recompila apenas os arquivos com um registro desatualizado
- Uma regra no arquivo make tem a seguinte forma:
target: requisitos ; comando
- Como que leio isso?
- Para construir o
target - Eu preciso primeiro construir os requisitos
- E depois executar tal comando
- Para construir o
Exemplo Makefile (1)
Arquivo Make
all: main
main:
g++ src/jogador.cpp src/carta.cpp src/baralho.cpp main.cpp -o meuprograma
clean:
rm meuprograma
- Aqui, para executar o comando
allprimeiro eu preciso executarmain(all: main) - Para executar o
maineu não preciso de nada- O main executa o comando:
g++ src/jogador.cpp src/carta.cpp src/baralho.cpp main.cpp -o main - Ou seja, compila o programa
- Gera um executável chamado de
meuprograma
- O main executa o comando:
- Para executar o
cleaneu não preciso de nada- O clean executa o comando
rmque apaga um arquivo - Estou apagamento o arquivo
meuprograma
- O clean executa o comando
Fazendo uso
- Para compilar seu código, execute o comando
make
make
- ou
make all
- Para limpar tudo
make clean
Exemplo Makefile (2)
- Abaixo temos outro Makefile mais complexo
- O mesmo faz uso de variáveis no começo
- Para referenciar umas variável, use
${NOME_DA_VAR}
Arquivo
CC=g++
CFLAGS=-std=c++20 -Wall
all: main
ponto.o: include/ponto.h src/ponto.cpp
${CC} ${CFLAGS} -c src/ponto.cpp
retangulo.o: ponto.o include/retangulo.h src/retangulo.cpp
${CC} ${CFLAGS} -c src/retangulo.cpp
main.o: include/ponto.h src/main.cpp
${CC} ${CFLAGS} -c src/main.cpp
main: main.o ponto.o retangulo.o
${CC} ${CFLAGS} -o main main.o ponto.o retangulo.o
clean:
rm -f main *.o
Entendendo
- O
all: maindepende do main - O
main: main.o ponto.o retangulo.odepende das regrasmain.o,ponto.oeretangulo.o. - A regra
ponto.o: include/ponto.h src/ponto.cppdepende dos arquivos existirem.- Isto é, quando uma dependência no make não é uma regra, o sistema simplesmente verifica se o arquivo existe
- Aqui
include/ponto.hesrc/ponto.cppsão dois arquivos que tem que existir para compilar o ponto - Faz sentido, são os arquivos que definem o código
- Para compilar execute:
${CC} ${CFLAGS} -c src/ponto.cpp, só que${CC}é uma variável, qual o valor dela?- Basta substituir com a definição no começo do arquivi
- Ou seja, para compilar execute
g++ -std=c++20 -Wall -c src/ponto.cpp - Dado que:
${CC}virag++e${CFLAGS}vira `-std=c++20 -Wall
Exemplo Makefile “Genérico”
- Make é um sistema poderoso que permite executar comandos
- Nosso foco aqui não é passar por todos esses comandos
- Possivelmente, um Makefile simples vai ser o suficiente para você na disciplina
- Porém, e como sou preguiçoso, tenho um Makefile genérico para uma organização de pastas da forma abaixo
- Copie e cole esse make, organize seu código em pastas similares que vai funcionar em sistemas unix
Como organizar os arquivos
. project
├── Makefile
├── include // aqui moram os seus .h ou .hpp
│ └── arq1.h
│ └── arq2.h
└── src // aqui moram os seus .cpp
│ └── arq1.cpp
│ └── arq2.cpp
└── third_party // aqui mora código de terceiros que você pegou da internet
│ └── doctest.h // vai ficar mais claro quando falarmos de testes
O makefile
CC := g++
SRCDIR := src
BUILDDIR := build
TARGET := main
SRCEXT := cpp
SOURCES := $(shell find $(SRCDIR) -type f -name *.$(SRCEXT))
OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.o))
CFLAGS := -g -Wall -O3 -std=c++20
INC := -I include/ -I third_party/
$(TARGET): $(OBJECTS)
$(CC) $^ -o $(TARGET)
$(BUILDDIR)/%.o: $(SRCDIR)/%.$(SRCEXT)
@mkdir -p $(@D)
$(CC) $(CFLAGS) $(INC) -c -o $@ $<
clean:
$(RM) -r $(BUILDDIR)/* $(TARGET)
.PHONY: clean
Considerações Finais
Qual o motivo de modularizar?
- Maior reusabilidade
- Melhoria da legibilidade
- Modificações facilitadas (e mais seguras)
- Maior confiabilidade
- Aumento da produtividade
Qual de usar um sistema de construção estilo o Make?
- Constriir o código em partes e de forma automatizada