Desafio C++

Mutex com RAII (lock_guard caseiro)

Enunciado

Implemente uma classe MeuLockGuard que recebe um std::mutex& no construtor, chama lock(), e no destrutor chama unlock(). Desabilite a cópia. No main, crie um std::mutex e uma função que usa MeuLockGuard para proteger uma seção crítica. Simule uma exceção dentro da seção crítica e verifique que o mutex é liberado.

Requisitos

  • Construtor recebe std::mutex& e chama lock().
  • Destrutor chama unlock().
  • Desabilitar cópia (construtor de cópia e operador de atribuição deletados).
  • Usar em uma função que lança exceção após adquirir o lock.

Código inicial

#include <iostream>
#include <mutex>
#include <stdexcept>

class MeuLockGuard {
    std::mutex& mtx_;
public:
    // TODO: construtor, destrutor, desabilitar cópia
};

void funcao_critica(std::mutex& mtx) {
    // TODO: usar MeuLockGuard e lançar exceção
}

int main() {
    std::mutex mtx;
    try {
        funcao_critica(mtx);
    } catch (const std::exception& e) {
        std::cerr << "Excecao: " << e.what() << '\n';
    }
    // Verificar que o mutex pode ser travado novamente
    mtx.lock();
    std::cout << "Mutex liberado corretamente\n";
    mtx.unlock();
    return 0;
}

Saída esperada

Dentro da seção crítica
Excecao: Erro simulado
Mutex liberado corretamente
Ver dica

O destrutor deve chamar mtx_.unlock(). Lembre-se de que std::mutex não é copiável, então a referência é essencial.

Mostrar solução
#include <iostream>
#include <mutex>
#include <stdexcept>

class MeuLockGuard {
    std::mutex& mtx_;
public:
    explicit MeuLockGuard(std::mutex& mtx) : mtx_(mtx) {
        mtx_.lock();
    }

    ~MeuLockGuard() {
        mtx_.unlock();
    }

    // Desabilita cópia
    MeuLockGuard(const MeuLockGuard&) = delete;
    MeuLockGuard& operator=(const MeuLockGuard&) = delete;
};

void funcao_critica(std::mutex& mtx) {
    MeuLockGuard guard(mtx);
    std::cout << "Dentro da seção crítica\n";
    // Simula uma exceção
    throw std::runtime_error("Erro simulado");
    // O destrutor de guard será chamado, liberando o mutex
}

int main() {
    std::mutex mtx;
    try {
        funcao_critica(mtx);
    } catch (const std::exception& e) {
        std::cerr << "Excecao: " << e.what() << '\n';
    }
    // Verifica que o mutex foi liberado
    mtx.lock();
    std::cout << "Mutex liberado corretamente\n";
    mtx.unlock();
    return 0;
}

Passo a passo

  1. A classe MeuLockGuard armazena uma referência a std::mutex.
  2. O construtor chama mtx_.lock() para adquirir o mutex.
  3. O destrutor chama mtx_.unlock() para liberar o mutex.
  4. Desabilitamos a cópia com = delete para evitar que o mutex seja liberado duas vezes.
  5. Na função funcao_critica, criamos um MeuLockGuard local, que adquire o mutex.
  6. Imprimimos uma mensagem e lançamos uma exceção.
  7. Durante o stack unwinding, o destrutor do guard é chamado, liberando o mutex.
  8. No main, capturamos a exceção e depois tentamos travar o mutex novamente para verificar que está livre.

Por que funciona

O MeuLockGuard encapsula a aquisição e liberação do mutex. Mesmo que uma exceção seja lançada, o destrutor é executado, garantindo que o mutex seja liberado. Isso evita deadlocks e é exatamente o que std::lock_guard faz.

Erros comuns

  • Esquecer de desabilitar a cópia: se o guard for copiado, dois objetos tentarão liberar o mesmo mutex, causando comportamento indefinido. Solução: = delete.
  • Não usar referência: se armazenar uma cópia do mutex, o lock será feito em uma cópia, não no mutex original. Use std::mutex&.
  • Lançar exceção antes de criar o guard: o mutex não estará travado, mas isso não é um erro do guard. Apenas garanta que o guard seja criado logo após a aquisição.
  • Esquecer de incluir <mutex>: erro de compilação. Sempre inclua os cabeçalhos necessários.

Outra forma de resolver

Usar std::lock_guard<std::mutex> diretamente, que é a solução padrão e mais segura. O MeuLockGuard é apenas para fins didáticos.

Saída esperada

Dentro da seção crítica
Excecao: Erro simulado
Mutex liberado corretamente