Partilhar: WhatsApp
aulify
UC UC02839 · T. Desenv. Software

Ficha 02 · C++ avançado

Exceptions, threads, move, CMake
Versão · Aluno
Tempo · 45 minutos
Aluno(a)
Turma
Data

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