UC02839

C/C++ avançado

Templates, STL, smart pointers, concorrência

Curso profissional · 50h · Linguagens de sistema

Plano

  1. Revisão e C++ moderno
  2. Templates e generics
  3. STL containers e algoritmos
  4. Smart pointers e RAII
  5. Lambdas e funcional
  6. Exceptions
  7. Concorrência (threads)
  8. Move semantics, otimização
  9. Build e testing modernos

Bloco 1 · C++ moderno

Por que C++ moderno

  • C++11 (2011) mudou a linguagem: auto, lambdas, smart pointers, threads.
  • C++14/17/20/23: ainda mais expressivo.
  • Pré-requisitos: dominar C básico (UC00669).
g++ -std=c++20 -Wall -Wextra programa.cpp -o programa

auto, range-for, init

auto x = 42;           // int
auto pi = 3.14;        // double
auto nome = "Ana";     // const char*

// Inicialização uniforme {}
vector<int> nums{1, 2, 3, 4, 5};
Pessoa p{"Ana", 25};

// Range-based for
for (auto n : nums) {
    cout << n << " ";
}

// Estruturado binding (C++17)
auto [chave, valor] = par;

Bloco 2 · Templates

Function templates

template <typename T>
T maximo(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    cout << maximo(3, 7) << endl;        // 7 (int)
    cout << maximo(2.5, 1.8) << endl;    // 2.5 (double)
    cout << maximo<string>("ana", "bia") << endl;  // bia
}

Templates = código genérico, instanciado em compile-time.

Class templates

template <typename T>
class Caixa {
private:
    T conteudo;
    
public:
    Caixa(T val) : conteudo(val) {}
    T obter() const { return conteudo; }
    void definir(T val) { conteudo = val; }
};

int main() {
    Caixa<int> c1(42);
    Caixa<string> c2("Olá");
    
    cout << c1.obter() << endl;   // 42
    cout << c2.obter() << endl;   // Olá
}

Variadic templates

template <typename T>
void imprimir(T arg) {
    cout << arg << endl;
}

template <typename T, typename... Args>
void imprimir(T primeiro, Args... resto) {
    cout << primeiro << " ";
    imprimir(resto...);
}

imprimir(1, "Ana", 3.14, true);
// 1 Ana 3.14 1

... = parameter pack (varios argumentos de tipos diferentes).

Bloco 3 · STL

Containers principais

#include <vector>
#include <list>
#include <map>
#include <set>
#include <unordered_map>
#include <deque>
#include <array>

vector<int> v{1, 2, 3};          // array dinâmico
list<int> l{1, 2, 3};            // lista ligada
deque<int> d{1, 2, 3};           // double-ended queue
array<int, 5> a{1, 2, 3, 4, 5};  // fixed array

map<string, int> m;              // árvore balanceada (ordenado)
unordered_map<string, int> um;   // hash table (rápido)

set<int> s;                      // únicos, ordenados
unordered_set<int> us;           // únicos, hash

vector — o mais usado

vector<int> v;

v.push_back(1);
v.push_back(2);
v.push_back(3);

v.size();        // 3
v.empty();       // false
v.front();       // 1
v.back();        // 3
v[0];            // 1
v.at(0);         // 1 (com bounds check)

v.pop_back();    // remove último
v.clear();       // esvazia

v.reserve(100);  // reserva capacidade (evita realloc)

// Range-based for
for (auto& n : v) {
    n *= 2;
}

map / unordered_map

map<string, int> idades;
idades["Ana"] = 25;
idades["Bruno"] = 30;

// Acesso
int idadeAna = idades["Ana"];
if (idades.contains("Carla")) { /* C++20 */ }

// Iterar (com structured binding)
for (const auto& [nome, idade] : idades) {
    cout << nome << ": " << idade << endl;
}

// Remover
idades.erase("Ana");

map = ordenado (árvore), O(log n).
unordered_map = hash, O(1) amortizado.

Algorithms

#include <algorithm>
#include <numeric>

vector<int> v{3, 1, 4, 1, 5, 9, 2, 6};

// Ordenar
sort(v.begin(), v.end());                     // ascending
sort(v.begin(), v.end(), greater<int>());     // descending

// Pesquisar
auto it = find(v.begin(), v.end(), 5);
if (it != v.end()) { /* encontrou */ }

// Counting
int n = count(v.begin(), v.end(), 1);

// Transformar
transform(v.begin(), v.end(), v.begin(), 
    [](int n) { return n * 2; });

// Acumular
int soma = accumulate(v.begin(), v.end(), 0);

// Min/max
auto [mn, mx] = minmax_element(v.begin(), v.end());

Bloco 4 · Smart pointers

Problema com raw pointers

void problema() {
    Pessoa* p = new Pessoa("Ana");
    // ... 100 linhas
    if (algo) return;  // LEAK!
    
    delete p;  // só se chegar aqui
}

Solução: smart pointers que se auto-deletam (RAII).

unique_ptr

#include <memory>

void exemplo() {
    auto p = make_unique<Pessoa>("Ana", 25);
    p->apresentar();
    
    // No fim do scope, ~unique_ptr() chama delete automaticamente
}

// Mover (não copiar)
auto p2 = move(p);   // p agora é nullptr

unique_ptr = ownership único, sem cópia.

shared_ptr e weak_ptr

auto p1 = make_shared<Pessoa>("Ana", 25);
auto p2 = p1;   // partilhar — incrementa refcount
auto p3 = p1;

cout << p1.use_count() << endl;  // 3

// ~Pessoa chamado quando o último shared_ptr morre

weak_ptr = referência fraca, evita ciclos:

shared_ptr<Pessoa> sp = make_shared<Pessoa>("Ana");
weak_ptr<Pessoa> wp = sp;

if (auto locked = wp.lock()) {  // converte para shared_ptr
    locked->apresentar();
}

Bloco 5 · Lambdas

Sintaxe

auto somar = [](int a, int b) { return a + b; };
cout << somar(2, 3) << endl;  // 5

// Capture by value
int x = 10;
auto add_x = [x](int n) { return n + x; };

// Capture by reference
auto incr = [&x]() { x++; };

// Capture all
auto f = [=](int n) { return n + x; };   // tudo por valor
auto g = [&](int n) { x = n; };          // tudo por ref

Lambdas com STL

vector<int> v{1, 2, 3, 4, 5, 6};

// Filter
vector<int> pares;
copy_if(v.begin(), v.end(), back_inserter(pares),
    [](int n) { return n % 2 == 0; });

// Sort customizado
vector<string> nomes{"Carla", "Ana", "Bruno"};
sort(nomes.begin(), nomes.end(),
    [](const string& a, const string& b) {
        return a.length() < b.length();
    });

// for_each
for_each(v.begin(), v.end(),
    [](int n) { cout << n << " "; });

Bloco 6 · Exceptions

Try/catch

#include <stdexcept>

try {
    if (idade < 0)
        throw invalid_argument("Idade negativa");
    if (idade > 150)
        throw out_of_range("Idade demasiado alta");
}
catch (const invalid_argument& e) {
    cerr << "Argumento inválido: " << e.what() << endl;
}
catch (const exception& e) {
    cerr << "Erro: " << e.what() << endl;
}

Custom exception

class SaldoInsuficiente : public runtime_error {
public:
    double saldo;
    
    SaldoInsuficiente(double s, const string& msg)
        : runtime_error(msg), saldo(s) {}
};

void sacar(double valor) {
    if (valor > saldo)
        throw SaldoInsuficiente(saldo, "Sem fundos");
}

try {
    sacar(1000);
} catch (const SaldoInsuficiente& e) {
    cerr << e.what() << " — saldo " << e.saldo << endl;
}

Bloco 7 · Threads

std::thread

#include <thread>

void trabalho(int id) {
    cout << "Thread " << id << " a correr" << endl;
}

int main() {
    thread t1(trabalho, 1);
    thread t2(trabalho, 2);
    
    t1.join();  // espera t1 acabar
    t2.join();
    
    cout << "Fim" << endl;
}

Mutex

#include <mutex>

mutex m;
int contador = 0;

void incrementar() {
    for (int i = 0; i < 1000; i++) {
        lock_guard<mutex> lock(m);
        contador++;
    }
}

int main() {
    thread t1(incrementar);
    thread t2(incrementar);
    t1.join();
    t2.join();
    cout << contador << endl;  // 2000 (garantido com lock)
}

lock_guard = RAII para mutex.

async / future

#include <future>

int trabalho_pesado() {
    this_thread::sleep_for(chrono::seconds(2));
    return 42;
}

int main() {
    auto futuro = async(launch::async, trabalho_pesado);
    
    cout << "A trabalhar..." << endl;
    
    int resultado = futuro.get();   // bloqueia até ter resultado
    cout << "Resultado: " << resultado << endl;
}

Bloco 8 · Move semantics

Rvalue references e std::move

vector<int> a{1, 2, 3, 4, 5};

vector<int> b = a;             // COPY (lento)
vector<int> c = move(a);       // MOVE (rápido — só transfere pointer)
// a agora vazio

Move evita cópias desnecessárias — essencial para performance.

Move constructor

class MeuVetor {
    int* dados;
    size_t tamanho;
    
public:
    // Move constructor
    MeuVetor(MeuVetor&& outro) noexcept
        : dados(outro.dados), tamanho(outro.tamanho)
    {
        outro.dados = nullptr;
        outro.tamanho = 0;
    }
    
    // Move assignment
    MeuVetor& operator=(MeuVetor&& outro) noexcept {
        if (this != &outro) {
            delete[] dados;
            dados = outro.dados;
            tamanho = outro.tamanho;
            outro.dados = nullptr;
            outro.tamanho = 0;
        }
        return *this;
    }
};

Bloco 9 · Build moderno

CMake

CMakeLists.txt:

cmake_minimum_required(VERSION 3.20)
project(MeuProjecto LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_executable(programa
    src/main.cpp
    src/utils.cpp
)

target_include_directories(programa PRIVATE include)

Build:

cmake -B build
cmake --build build
./build/programa

Testes com Catch2

#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
#include "calculadora.h"

TEST_CASE("Somar funciona") {
    Calculadora c;
    REQUIRE(c.somar(2, 3) == 5);
    REQUIRE(c.somar(0, 0) == 0);
}

TEST_CASE("Dividir por zero lança") {
    Calculadora c;
    REQUIRE_THROWS_AS(c.dividir(10, 0), invalid_argument);
}

UC02839 · resumo

  • C++ moderno (C++11+) = transformação completa da linguagem.
  • auto, range-for, lambdas = código compacto e expressivo.
  • Templates = código genérico em compile-time.
  • STL = vector, map, algorithm — tudo pronto, optimizado.
  • Smart pointers = adeus memory leaks (RAII).
  • std::thread + mutex para concorrência.
  • Move semantics = performance sem cópias.
  • CMake + Catch2 = build e testes modernos.
  • Base para Unreal Engine, sistemas embedded sérios, trading.

NOTAS DO PROFESSOR 📖 C++ moderno é uma linguagem diferente O ponto de partida desta UC é que o C++ que importa hoje começa em 2011: o C++11 introduziu auto, lambdas, smart pointers e threads, transformando a forma de escrever a linguagem. Quem aprendeu C++ "à antiga" tem de desaprender alguns hábitos. As flags de compilação (-Wall -Wextra) devem ser apresentadas como obrigatórias, não opcionais: ativar todos os avisos é a primeira linha de defesa contra bugs. 🗣️ Pontos a desenvolver oralmente • Explicar porque se exige a base de C (UC00669) antes deste avançado • Justificar -std=c++20 e o hábito de compilar sempre com -Wall -Wextra • Dar a perspetiva histórica das versões (11, 14, 17, 20, 23) e o que cada uma trouxe

NOTAS DO PROFESSOR 📖 auto, range-for e a sintaxe expressiva Estas três funcionalidades reduzem drasticamente o ruído do código C++ clássico. O auto deduz o tipo em compile-time, mantendo a tipagem forte sem a verbosidade; o range-for elimina a gestão manual de índices; o structured binding desempacota pares e tuplos com elegância. É importante avisar que auto não significa "tipagem dinâmica": o tipo continua fixo e verificado pelo compilador. 🗣️ Pontos a desenvolver oralmente • Esclarecer que auto é dedução de tipo, não tipagem dinâmica • Comparar um for clássico com índice e o range-based for equivalente • Mostrar a inicialização uniforme com {} e os seus benefícios

NOTAS DO PROFESSOR 📖 Function templates: escrever uma vez, servir muitos tipos Os templates resolvem o problema de repetir a mesma lógica para int, double, string, etc. A ideia-chave a fixar é que a instanciação acontece em tempo de compilação: o compilador gera uma versão da função para cada tipo usado, pelo que não há custo em runtime. Isto distingue os templates do C++ dos generics de outras linguagens e é a base de toda a STL. 🗣️ Pontos a desenvolver oralmente • Explicar "instanciação em compile-time" e a ausência de custo em runtime • Mostrar a dedução automática do tipo T vs a especificação explícita maximo<string> • Avisar sobre os requisitos implícitos do tipo (aqui, suportar o operador >)

NOTAS DO PROFESSOR 📖 Class templates: tipos genéricos Se as function templates generalizam funções, as class templates generalizam tipos inteiros. A Caixa<T> é um exemplo deliberadamente simples para que o formando veja que o mesmo padrão da STL (vector<int>, map<string,int>) é algo que ele próprio pode escrever. Vale a pena ligar este conceito ao bloco seguinte: a STL é, no essencial, uma vasta coleção de class templates bem desenhadas. 🗣️ Pontos a desenvolver oralmente • Relacionar Caixa<int> com vector<int>: a mesma mecânica de template • Explicar a sintaxe do construtor e dos métodos dentro de uma class template • Reforçar que cada instanciação (Caixa<int>, Caixa<string>) gera uma classe distinta

NOTAS DO PROFESSOR 📖 Variadic templates: número variável de argumentos Este é o tópico mais avançado do bloco e convém apresentá-lo com calma. O parameter pack permite funções com um número arbitrário de argumentos de tipos diferentes, resolvidos recursivamente em compile-time. É a tecnologia por trás de coisas que os formandos já usaram, como o cout encadeado ou o printf seguro. Não é preciso dominá-lo já, mas reconhecê-lo quando aparecer em bibliotecas. 🗣️ Pontos a desenvolver oralmente • Traçar a recursão: cada chamada "consome" um argumento e passa o resto • Apontar a necessidade do caso-base (a primeira sobrecarga de imprimir) • Enquadrar como tópico de reconhecimento, não de domínio imediato

NOTAS DO PROFESSOR 📖 Conhecer os containers e escolher bem A STL oferece vários containers e a competência profissional está em escolher o certo para cada problema. O critério-chave é o padrão de acesso: vector para acesso por índice, list para inserções no meio, map para chaves ordenadas, unordered_map para procura rápida por chave. Convém adiantar que a escolha errada não dá erro de compilação, dá lentidão, e por isso exige compreender as características de cada um. 🗣️ Pontos a desenvolver oralmente • Distinguir containers sequenciais (vector, list, deque) de associativos (map, set) • Explicar a diferença prática entre map (ordenado, árvore) e unordered_map (hash) • Dar uma regra simples: na dúvida, começar por vector

NOTAS DO PROFESSOR 📖 vector: o container do dia a dia Se há um container que o formando vai usar constantemente, é o vector. Vale a pena treinar bem a sua interface, mas sobretudo dois pontos: a diferença entre operator[] (rápido, sem verificação) e at() (verifica limites, lança exceção), e o papel de reserve() para evitar realocações custosas quando se sabe o tamanho à partida. O detalhe do auto& no range-for é crucial: sem o &, modifica-se uma cópia. 🗣️ Pontos a desenvolver oralmente • Contrastar v[i] com v.at(i) em termos de segurança vs performance • Explicar como cresce a capacidade e porque reserve() evita realocações • Frisar o auto& vs auto no range-for ao querer modificar os elementos

NOTAS DO PROFESSOR 📖 map vs unordered_map: a decisão de complexidade Este slide é onde a teoria de complexidade encontra a prática. A escolha entre map e unordered_map é o exemplo clássico de trade-off: ordenação garantida e O(log n) versus acesso O(1) amortizado mas sem ordem. Há um perigo subtil a alertar: aceder a uma chave inexistente com operator[] cria-a silenciosamente; usar contains() (C++20) ou find() é mais seguro para apenas consultar. 🗣️ Pontos a desenvolver oralmente • Explicar O(log n) vs O(1) amortizado com números concretos de elementos • Avisar que idades["Carla"] cria a entrada se não existir (armadilha comum) • Mostrar o structured binding [nome, idade] na iteração como C++ idiomático

NOTAS DO PROFESSOR 📖 Algorithms: não reinventar a roda O cabeçalho <algorithm> é talvez a parte mais subestimada da STL. A mensagem central é: antes de escrever um ciclo à mão para ordenar, procurar, contar ou transformar, há quase de certeza um algoritmo pronto, testado e otimizado. Dominar o par iteradores begin()/end() é a chave que abre toda esta biblioteca, e combinar algorithms com lambdas (bloco seguinte) é onde o C++ moderno fica verdadeiramente poderoso. 🗣️ Pontos a desenvolver oralmente • Defender o princípio "não escrever um loop quando há um algorithm" • Explicar o papel dos iteradores begin()/end() como interface universal • Antecipar a combinação algorithm + lambda como ponte para o bloco 5

NOTAS DO PROFESSOR 📖 O problema que os smart pointers resolvem Antes de mostrar a solução, é essencial que o formando sinta a dor do problema. Este exemplo de memory leak — um return a meio que salta o delete — é exatamente o tipo de bug silencioso que assombra código C com ponteiros crus. Convém introduzir já o conceito de RAII (Resource Acquisition Is Initialization): amarrar o tempo de vida de um recurso ao tempo de vida de um objeto, deixando o destrutor limpar tudo. 🗣️ Pontos a desenvolver oralmente • Mostrar como um return ou uma exceção a meio causa o leak no exemplo • Introduzir RAII como o princípio central da gestão de recursos em C++ • Ligar leaks a problemas reais: programas que consomem memória até crashar

NOTAS DO PROFESSOR 📖 unique_ptr: posse exclusiva O unique_ptr é o smart pointer a usar por omissão. O conceito-chave é a posse única: só um unique_ptr pode possuir o recurso, pelo que não é copiável, apenas transferível com std::move. O detalhe a sublinhar é que make_unique é a forma preferida de o criar e que, no fim do scope, o destrutor liberta a memória automaticamente, sem o programador escrever um único delete. 🗣️ Pontos a desenvolver oralmente • Explicar porque não se pode copiar um unique_ptr (quebraria a posse única) • Mostrar que após move(p) o ponteiro original fica a nullptr • Recomendar make_unique como a forma segura e idiomática de criar

NOTAS DO PROFESSOR 📖 shared_ptr e weak_ptr: posse partilhada Quando vários donos precisam de partilhar um recurso, entra o shared_ptr, com contagem de referências: o objeto morre só quando o último dono desaparece. O problema clássico é o ciclo de referências (dois objetos que se apontam mutuamente nunca chegam a zero), e a solução é o weak_ptr, uma referência que não conta para a posse. Convém ser honesto: shared_ptr tem custo, e não deve ser o reflexo automático quando unique_ptr chega. 🗣️ Pontos a desenvolver oralmente • Demonstrar o use_count() a subir e descer conforme se copiam shared_ptr • Explicar o ciclo de referências e como o weak_ptr o quebra • Avisar que shared_ptr tem overhead e que unique_ptr deve ser a escolha por defeito

NOTAS DO PROFESSOR 📖 Lambdas e a captura de contexto As lambdas são funções anónimas definidas no local de uso, e o que as distingue é a cláusula de captura entre []. O ponto mais importante e mais propenso a erro é a diferença entre capturar por valor [x] (uma cópia, congelada no momento) e por referência [&x] (acesso ao original, que pode mudar). Capturar por referência exige cuidado com o tempo de vida: a variável tem de existir enquanto a lambda for usada. 🗣️ Pontos a desenvolver oralmente • Distinguir captura por valor (cópia) de captura por referência (acesso ao original) • Alertar para o perigo de capturar por referência variáveis que deixam de existir • Explicar as capturas globais [=] e [&] e quando são apropriadas

NOTAS DO PROFESSOR 📖 Lambdas com STL: a combinação poderosa É aqui que tudo se junta: os algorithms do bloco 3 ganham toda a sua flexibilidade quando alimentados com lambdas. Em vez de criar funções nomeadas separadas, define-se o critério ali mesmo, junto ao uso: o predicado de copy_if, o comparador de sort, a ação de for_each. Esta é a forma idiomática moderna de processar coleções em C++ e aproxima-se do estilo funcional de outras linguagens. 🗣️ Pontos a desenvolver oralmente • Mostrar a lambda como o "critério" que personaliza um algorithm genérico • Explicar o back_inserter no copy_if (insere sem pré-dimensionar o destino) • Comparar com a abordagem antiga de escrever funções nomeadas à parte

NOTAS DO PROFESSOR 📖 Exceptions: tratar o erro onde faz sentido As exceções permitem separar a lógica normal do tratamento de erros, em vez de verificar códigos de retorno a cada passo. Dois pontos técnicos a fixar: apanhar sempre por referência const (const exception& e) para evitar cópias e slicing, e ordenar os catch do mais específico para o mais geral, pois o primeiro compatível é o que dispara. A hierarquia de std::exception é a base sobre a qual tudo isto assenta. 🗣️ Pontos a desenvolver oralmente • Explicar porque se apanha por referência const e não por valor • Mostrar a ordem dos catch: específico antes de genérico • Apresentar e.what() como a mensagem da exceção e a hierarquia de std::exception

NOTAS DO PROFESSOR 📖 Exceções personalizadas com contexto Herdar de std::runtime_error permite criar exceções específicas do domínio que carregam informação extra — aqui, o saldo disponível no momento do erro. Isto é boas práticas profissionais: em vez de uma mensagem genérica, a exceção transporta os dados que quem a apanha precisa para reagir. Convém ligar este exemplo ao mundo real, mostrando como sistemas financeiros e de negócio definem hierarquias de exceções próprias. 🗣️ Pontos a desenvolver oralmente • Explicar a herança de runtime_error e a chamada ao construtor da base • Realçar o valor de a exceção carregar dados de contexto (o saldo) • Dar exemplos de hierarquias de exceções em sistemas reais

NOTAS DO PROFESSOR 📖 std::thread: executar em paralelo A concorrência permite aproveitar os múltiplos núcleos dos processadores modernos. O conceito essencial deste primeiro contacto é o join(): a thread principal tem de esperar que as threads filhas terminem antes de prosseguir, ou o programa pode terminar com threads ainda a correr. Convém avisar desde já que partilhar dados entre threads é onde os problemas começam, preparando o terreno para o slide do mutex. 🗣️ Pontos a desenvolver oralmente • Explicar o que faz join() e o que acontece se for esquecido • Distinguir paralelismo (vários núcleos) de concorrência (intercalado) • Antecipar que dados partilhados sem proteção causam bugs imprevisíveis

NOTAS DO PROFESSOR 📖 Mutex: proteger dados partilhados Este slide responde ao perigo anunciado: quando várias threads tocam na mesma variável, surgem as race conditions, e o resultado deixa de ser determinístico. O mutex garante que só uma thread acede de cada vez à zona crítica. O detalhe elegante é o lock_guard, que aplica RAII ao mutex — tranca ao construir, destranca ao sair do scope, mesmo que ocorra uma exceção, eliminando o risco de esquecer o unlock. 🗣️ Pontos a desenvolver oralmente • Explicar race condition: porque contador++ sem mutex pode perder incrementos • Mostrar que lock_guard liberta o mutex automaticamente ao sair do scope • Avisar para o deadlock como o reverso do problema (trancar a mais)

NOTAS DO PROFESSOR 📖 async e future: concorrência de mais alto nível O par async/future é uma abstração mais confortável que gerir threads e mutexes à mão: lança-se uma tarefa, continua-se a trabalhar, e recolhe-se o resultado quando for preciso com get(). A ideia-chave é que get() bloqueia apenas no momento em que o valor é mesmo necessário, permitindo sobrepor trabalho útil entretanto. É a forma recomendada de paralelizar uma computação que devolve um resultado. 🗣️ Pontos a desenvolver oralmente • Mostrar que o programa continua a trabalhar enquanto a tarefa corre em paralelo • Explicar que get() bloqueia só quando o resultado é realmente preciso • Contrastar com std::thread: async gere a thread e devolve o resultado por si

NOTAS DO PROFESSOR 📖 Move semantics: transferir em vez de copiar Esta é uma das ideias mais importantes do C++ moderno para a performance. Copiar um vetor grande duplica toda a memória; movê-lo apenas transfere o ponteiro interno, deixando a origem vazia. O ponto a interiorizar é quando o move é seguro e desejável: quando já não precisamos do objeto original. std::move não move nada por si — apenas marca um objeto como "movível", autorizando a transferência. 🗣️ Pontos a desenvolver oralmente • Esclarecer que std::move não move, apenas converte para rvalue (autoriza o move) • Avisar que a origem fica num estado válido mas indefinido (aqui, vazia) • Quantificar a diferença: copiar é O(n), mover é O(1)

NOTAS DO PROFESSOR 📖 Implementar move constructor e move assignment Este slide mostra o que acontece por baixo do capô quando o move funciona. O padrão é sempre o mesmo: roubar os recursos do outro objeto (copiar o ponteiro e o tamanho) e deixá-lo num estado nulo seguro. Dois detalhes profissionais a destacar: o noexcept (que permite à STL otimizar usando move em vez de cópia) e a verificação this != &outro no assignment, para evitar autodestruição na auto-atribuição. 🗣️ Pontos a desenvolver oralmente • Explicar o padrão "roubar e anular" passo a passo • Justificar o noexcept e o seu impacto em otimizações da STL • Apontar a verificação de auto-atribuição (this != &outro) e porque é necessária

NOTAS DO PROFESSOR 📖 CMake: o standard de build em C++ Compilar à mão com g++ funciona para um ficheiro, mas projetos reais têm dezenas de ficheiros, dependências e configurações. O CMake é a ferramenta de facto para gerir isto de forma portável entre sistemas operativos e compiladores. Não é preciso dominar CMake em profundidade, mas saber ler e escrever um CMakeLists.txt básico é uma competência esperada de qualquer programador C++ empregável. 🗣️ Pontos a desenvolver oralmente • Explicar porque compilar à mão deixa de escalar em projetos reais • Percorrer as linhas do CMakeLists: projeto, standard, executável, includes • Mostrar o fluxo de dois passos: configurar (cmake -B) e construir (cmake --build)

NOTAS DO PROFESSOR 📖 Testes automáticos com Catch2 Código profissional vem acompanhado de testes, e o C++ tem frameworks como o Catch2 que o tornam simples. O ponto pedagógico é que um teste é uma afirmação executável sobre o comportamento esperado: REQUIRE verifica um resultado, REQUIRE_THROWS_AS verifica que uma exceção certa é lançada. Testar não é perda de tempo — é o que permite refatorar e evoluir código com confiança, sabendo que se algo partir o teste avisa. 🗣️ Pontos a desenvolver oralmente • Explicar que cada TEST_CASE documenta e verifica um comportamento • Mostrar como testar tanto o caso normal como o caso de erro (exceção) • Defender o valor dos testes ao refatorar: a rede de segurança do programador

NOTAS DO PROFESSOR 📖 Síntese: o arsenal do C++ moderno Este resumo recolhe um percurso exigente, de templates a concorrência. A mensagem a deixar é que estas funcionalidades não são curiosidades académicas: são o que separa o C++ legado do C++ que se escreve hoje na indústria de jogos, sistemas embebidos e finança de alta performance. Vale a pena fechar apontando os domínios profissionais concretos onde estas competências valem salários elevados e são genuinamente procuradas. 🗣️ Pontos a desenvolver oralmente • Recapitular a linha condutora: expressividade (auto, lambdas), genéricos (templates), segurança (smart pointers, RAII), performance (move) • Ligar cada bloco a uma aplicação real (Unreal, embedded, trading) • Motivar a continuação: estas bases abrem portas a áreas bem remuneradas