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 chamalock(). - 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
- A classe
MeuLockGuardarmazena uma referência astd::mutex. - O construtor chama
mtx_.lock()para adquirir o mutex. - O destrutor chama
mtx_.unlock()para liberar o mutex. - Desabilitamos a cópia com
= deletepara evitar que o mutex seja liberado duas vezes. - Na função
funcao_critica, criamos umMeuLockGuardlocal, que adquire o mutex. - Imprimimos uma mensagem e lançamos uma exceção.
- Durante o stack unwinding, o destrutor do
guardé chamado, liberando o mutex. - 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