Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CPlusPlusNotesForProfessionals.pdf
Скачиваний:
47
Добавлен:
20.05.2023
Размер:
5.11 Mб
Скачать

Chapter 83: RAII: Resource Acquisition Is

Initialization

Section 83.1: Locking

Bad locking:

std::mutex mtx;

void bad_lock_example() { mtx.lock();

try

{

foo(); bar();

if (baz()) {

mtx.unlock(); // Have to unlock on each exit point.

return;

 

}

 

quux();

 

mtx.unlock();

// Normal unlock happens here.

}

 

catch(...) {

 

mtx.unlock();

// Must also force unlock in the presence of

throw;

// exceptions and allow the exception to continue.

}

 

}

 

That is the wrong way to implement the locking and unlocking of the mutex. To ensure the correct release of the mutex with unlock() requires the programer to make sure that all the flows resulting in the exiting of the function result in a call to unlock(). As shown above this is a brittle processes as it requires any maintainers to continue following the pattern manually.

Using an appropriately crafted class to implement RAII, the problem is trivial:

std::mutex mtx;

void good_lock_example() {

std::lock_guard<std::mutex> lk(mtx); // constructor locks.

//destructor unlocks. destructor call

//guaranteed by language.

foo(); bar();

if (baz()) { return;

}

quux();

}

lock_guard is an extremely simple class template that simply calls lock() on its argument in its constructor, keeps a reference to the argument, and calls unlock() on the argument in its destructor. That is, when the lock_guard goes out of scope, the mutex is guaranteed to be unlocked. It doesn't matter if the reason it went out of scope is an exception or an early return - all cases are handled; regardless of the control flow, we have guaranteed that we will unlock correctly.

GoalKicker.com – C++ Notes for Professionals

451

Section 83.2: ScopeSuccess (c++17)

Version ≥ C++17

Thanks to int std::uncaught_exceptions(), we can implement action which executes only on success (no thrown

exception in scope). Previously bool std::uncaught_exception() just allows to detect if any stack unwinding is running.

#include <exception> #include <iostream>

template <typename F> class ScopeSuccess

{

private: F f;

int uncaughtExceptionCount = std::uncaught_exceptions(); public:

explicit ScopeSuccess(const F& f) : f(f) {} ScopeSuccess(const ScopeSuccess&) = delete; ScopeSuccess& operator =(const ScopeSuccess&) = delete;

// f() might throw, as it can be caught normally. ~ScopeSuccess() noexcept(noexcept(f())) {

if (uncaughtExceptionCount == std::uncaught_exceptions()) { f();

}

}

};

struct Foo { ~Foo() {

try {

ScopeSuccess logSuccess{[](){std::cout << "Success 1\n";}};

//Scope succeeds,

//even if Foo is destroyed during stack unwinding

//(so when 0 < std::uncaught_exceptions())

//(or previously std::uncaught_exception() == true)

}catch (...) {

}

try {

ScopeSuccess logSuccess{[](){std::cout << "Success 2\n";}};

throw std::runtime_error("Failed"); // returned value

// of std::uncaught_exceptions increases } catch (...) { // returned value of std::uncaught_exceptions decreases

}

}

};

int main()

{

try {

Foo foo;

throw std::runtime_error("Failed"); // std::uncaught_exceptions() == 1 } catch (...) { // std::uncaught_exceptions() == 0

}

}

GoalKicker.com – C++ Notes for Professionals

452

Output:

Success 1

Section 83.3: ScopeFail (c++17)

Version ≥ C++17

Thanks to int std::uncaught_exceptions(), we can implement action which executes only on failure (thrown

exception in scope). Previously bool std::uncaught_exception() just allows to detect if any stack unwinding is running.

#include <exception> #include <iostream>

template <typename F> class ScopeFail

{

private: F f;

int uncaughtExceptionCount = std::uncaught_exceptions(); public:

explicit ScopeFail(const F& f) : f(f) {} ScopeFail(const ScopeFail&) = delete;

ScopeFail& operator =(const ScopeFail&) = delete;

// f() should not throw, else std::terminate is called. ~ScopeFail() {

if (uncaughtExceptionCount != std::uncaught_exceptions()) { f();

}

}

};

struct Foo { ~Foo() {

try {

ScopeFail logFailure{[](){std::cout << "Fail 1\n";}};

//Scope succeeds,

//even if Foo is destroyed during stack unwinding

//(so when 0 < std::uncaught_exceptions())

//(or previously std::uncaught_exception() == true)

}catch (...) {

}

try {

ScopeFail logFailure{[](){std::cout << "Failure 2\n";}};

throw std::runtime_error("Failed"); // returned value

// of std::uncaught_exceptions increases } catch (...) { // returned value of std::uncaught_exceptions decreases

}

}

};

int main()

{

try {

Foo foo;

GoalKicker.com – C++ Notes for Professionals

453

throw std::runtime_error("Failed"); // std::uncaught_exceptions() == 1 } catch (...) { // std::uncaught_exceptions() == 0

}

}

Output:

Failure 2

Section 83.4: Finally/ScopeExit

For cases when we don't want to write special classes to handle some resource, we may write a generic class:

template<typename Function> class Finally final

{

public:

explicit Finally(Function f) : f(std::move(f)) {} ~Finally() { f(); } // (1) See below

Finally(const Finally&) = delete; Finally(Finally&&) = default;

Finally& operator =(const Finally&) = delete; Finally& operator =(Finally&&) = delete;

private: Function f;

};

// Execute the function f when the returned object goes out of scope. template<typename Function>

auto onExit(Function &&f) { return Finally<std::decay_t<Function>>{std::forward<Function>(f)}; }

And its example usage

void foo(std::vector<int>& v, int i)

{

// ...

v[i] += 42;

auto autoRollBackChange = onExit([&](){ v[i] -= 42; });

// ... code as recursive call `foo(v, i + 1)`

}

Note (1): Some discussion about destructor definition has to be considered to handle exception:

~Finally() noexcept { f(); }: std::terminate is called in case of exception

~Finally() noexcept(noexcept(f())) { f(); }: terminate() is called only in case of exception during stack unwinding.

~Finally() noexcept { try { f(); } catch (...) { /* ignore exception (might log it) */} } No std::terminate called, but we cannot handle error (even for non stack unwinding).

GoalKicker.com – C++ Notes for Professionals

454