Ficha 02 · C++ avançado
Exercício 1 · Custom exception
Cria classe Validacao com método static void verificarIdade(int) que lança IdadeInvalida (custom, deriva de invalid_argument) se idade < 0 ou > 150. Demonstra com 3 casos.
Resposta:
#include <iostream>
#include <stdexcept>
using namespace std;
class IdadeInvalida : public invalid_argument {
public:
int valor;
IdadeInvalida(int v, const string& msg)
: invalid_argument(msg), valor(v) {}
};
class Validacao {
public:
static void verificarIdade(int idade) {
if (idade < 0)
throw IdadeInvalida(idade, "Idade negativa");
if (idade > 150)
throw IdadeInvalida(idade, "Idade > 150");
}
};
int main() {
int testes[] = {25, -5, 200};
for (int idade : testes) {
try {
Validacao::verificarIdade(idade);
cout << "OK: " << idade << endl;
} catch (const IdadeInvalida& e) {
cout << "❌ " << e.valor << ": " << e.what() << endl;
}
}
}
Exercício 2 · 2 threads simples
Cria dois threads que cada um imprime os números de 1 a 5. Usa join() para esperar por ambos.
Resposta:
#include <iostream>
#include <thread>
#include <chrono>
using namespace std;
void contar(int id) {
for (int i = 1; i <= 5; i++) {
cout << "T" << id << ": " << i << endl;
this_thread::sleep_for(chrono::milliseconds(100));
}
}
int main() {
thread t1(contar, 1);
thread t2(contar, 2);
t1.join();
t2.join();
cout << "Fim" << endl;
}
Compilar:
g++ -std=c++20 -pthread programa.cpp -o programa
Exercício 3 · Mutex + contador
Cria 4 threads que cada um incrementa um contador partilhado 10000 vezes. Sem mutex, mostra que o resultado é errático. Com lock_guard, mostra que dá 40000 garantido.
Resposta:
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
using namespace std;
mutex m;
long contador = 0;
void incrementar_sem_lock() {
for (int i = 0; i < 10000; i++) {
contador++;
}
}
void incrementar_com_lock() {
for (int i = 0; i < 10000; i++) {
lock_guard<mutex> lock(m);
contador++;
}
}
int main() {
// Sem mutex
contador = 0;
vector<thread> ts;
for (int i = 0; i < 4; i++)
ts.emplace_back(incrementar_sem_lock);
for (auto& t : ts) t.join();
cout << "Sem mutex: " << contador << " (esperado 40000)\n";
// Com mutex
contador = 0;
ts.clear();
for (int i = 0; i < 4; i++)
ts.emplace_back(incrementar_com_lock);
for (auto& t : ts) t.join();
cout << "Com mutex: " << contador << " (esperado 40000)\n";
}
Exercício 4 · async/future
Cria função int fibonacci(int n) recursiva. No main, calcula fibonacci(40) e fibonacci(38) em paralelo usando async. Imprime ambos.
Resposta:
#include <iostream>
#include <future>
#include <chrono>
using namespace std;
long fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
int main() {
auto start = chrono::steady_clock::now();
auto f1 = async(launch::async, fibonacci, 40);
auto f2 = async(launch::async, fibonacci, 38);
cout << "fib(40) = " << f1.get() << endl;
cout << "fib(38) = " << f2.get() << endl;
auto end = chrono::steady_clock::now();
auto ms = chrono::duration_cast<chrono::milliseconds>(end - start).count();
cout << "Tempo: " << ms << " ms\n";
}
Exercício 5 · Move semantics
Cria classe BigBuffer com unique_ptr<int[]> interno (1 milhão de ints). Mede o tempo de copy vs move (com chrono).
Resposta:
#include <iostream>
#include <memory>
#include <chrono>
using namespace std;
class BigBuffer {
public:
unique_ptr<int[]> dados;
size_t n;
BigBuffer(size_t s) : n(s) {
dados = make_unique<int[]>(n);
for (size_t i = 0; i < n; i++) dados[i] = i;
}
// Copy explícito
BigBuffer copiar() const {
BigBuffer copia(n);
for (size_t i = 0; i < n; i++) copia.dados[i] = dados[i];
return copia;
}
// Move (default funciona porque temos unique_ptr)
BigBuffer(BigBuffer&&) = default;
BigBuffer& operator=(BigBuffer&&) = default;
};
int main() {
const size_t N = 1'000'000;
BigBuffer original(N);
// Copy
auto t1 = chrono::steady_clock::now();
BigBuffer copy = original.copiar();
auto t2 = chrono::steady_clock::now();
auto us_copy = chrono::duration_cast<chrono::microseconds>(t2 - t1).count();
cout << "Copy: " << us_copy << " µs\n";
// Move
auto t3 = chrono::steady_clock::now();
BigBuffer moved = std::move(copy);
auto t4 = chrono::steady_clock::now();
auto us_move = chrono::duration_cast<chrono::microseconds>(t4 - t3).count();
cout << "Move: " << us_move << " µs\n";
}
Move típico < 1µs vs cópia em milissegundos.
Exercício 6 · CMake básico
Cria projecto meu_app com 3 ficheiros: main.cpp, utils.cpp, utils.h. Escreve CMakeLists.txt que compila tudo com C++20 e warnings. Demonstra build out-of-source.
Resposta:
Estrutura:
meu_app/
├── CMakeLists.txt
├── src/
│ ├── main.cpp
│ └── utils.cpp
└── include/
└── utils.h
include/utils.h:
#pragma once
int somar(int a, int b);
src/utils.cpp:
#include "utils.h"
int somar(int a, int b) { return a + b; }
src/main.cpp:
#include <iostream>
#include "utils.h"
int main() {
std::cout << somar(2, 3) << std::endl;
}
CMakeLists.txt:
cmake_minimum_required(VERSION 3.20)
project(meu_app LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(meu_app
src/main.cpp
src/utils.cpp
)
target_include_directories(meu_app PRIVATE include)
target_compile_options(meu_app PRIVATE -Wall -Wextra)
Build:
cmake -B build
cmake --build build
./build/meu_app # 5
Exercício 7 · Testes com Catch2
Adiciona ao projecto do exercício 6 testes para somar() usando Catch2 via FetchContent.
Resposta:
CMakeLists.txt actualizado:
cmake_minimum_required(VERSION 3.20)
project(meu_app LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_library(utils STATIC src/utils.cpp)
target_include_directories(utils PUBLIC include)
add_executable(meu_app src/main.cpp)
target_link_libraries(meu_app PRIVATE utils)
# Testes
include(FetchContent)
FetchContent_Declare(
Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.5.0
)
FetchContent_MakeAvailable(Catch2)
enable_testing()
add_executable(tests tests/test_utils.cpp)
target_link_libraries(tests PRIVATE utils Catch2::Catch2WithMain)
add_test(NAME utils_tests COMMAND tests)
tests/test_utils.cpp:
#include <catch2/catch_test_macros.hpp>
#include "utils.h"
TEST_CASE("Somar funciona", "[utils]") {
REQUIRE(somar(2, 3) == 5);
REQUIRE(somar(0, 0) == 0);
REQUIRE(somar(-1, 1) == 0);
REQUIRE(somar(-5, -5) == -10);
}
cmake -B build
cmake --build build
cd build && ctest --output-on-failure
Exercício 8 · Mini-projecto integrado
Cria uma aplicação que:
1. Lê palavras de um ficheiro de texto.
2. Em paralelo (com async), conta frequências em metades do ficheiro.
3. Combina resultados.
4. Imprime top 10 palavras mais frequentes.
Resposta:
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <future>
using namespace std;
using Contagem = unordered_map<string, int>;
Contagem contar(const vector<string>& palavras, size_t inicio, size_t fim) {
Contagem c;
for (size_t i = inicio; i < fim; i++) {
c[palavras[i]]++;
}
return c;
}
int main() {
ifstream f("texto.txt");
if (!f) { cerr << "Erro abertura\n"; return 1; }
// Ler todas as palavras
vector<string> palavras;
string p;
while (f >> p) palavras.push_back(p);
cout << "Total palavras: " << palavras.size() << endl;
// Dividir em 2 e processar em paralelo
size_t meio = palavras.size() / 2;
auto f1 = async(launch::async, contar, ref(palavras), 0, meio);
auto f2 = async(launch::async, contar, ref(palavras), meio, palavras.size());
Contagem combinado = f1.get();
for (const auto& [palavra, n] : f2.get()) {
combinado[palavra] += n;
}
// Top 10
vector<pair<string, int>> v(combinado.begin(), combinado.end());
sort(v.begin(), v.end(),
[](const auto& a, const auto& b) {
return a.second > b.second;
});
cout << "\nTop 10:\n";
for (size_t i = 0; i < min<size_t>(10, v.size()); i++) {
cout << " " << v[i].first << ": " << v[i].second << endl;
}
}
Compilar:
g++ -std=c++20 -pthread -O2 programa.cpp -o programa
echo "a b c a b a c d a b" > texto.txt
./programa