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