Поиск…


замечания

RAII означает R esource A cquisition I s I nitialization. Также иногда упоминается как SBRM (управление ресурсами на основе Scope) или RRID (Release Release Is Destruction), RAII - это идиома, используемая для привязки ресурсов к времени жизни объекта. В C ++ деструктор для объекта всегда выполняется, когда объект выходит из области видимости - мы можем воспользоваться этим, чтобы связать очистку ресурсов с уничтожением объектов.

Каждый раз, когда вам нужно приобрести некоторый ресурс (например, блокировку, дескриптор файла, выделенный буфер), который вам в конечном итоге нужно будет выпускать, вам следует рассмотреть возможность использования объекта для управления этим управлением ресурсами. Развертывание стека будет происходить независимо от исключения или выхода на ранней стадии, поэтому объект обработчика ресурсов очистит ресурс для вас, не задумываясь о всех возможных текущих и будущих кодах.

Стоит отметить, что RAII не полностью освобождает разработчика от мысли о ресурсе жизни. Один случай - это, очевидно, вызов crash или exit (), который предотвратит вызов деструкторов. Поскольку ОС будет очищать локальные ресурсы процесса, такие как память, после завершения процесса, это не проблема в большинстве случаев. Однако с системными ресурсами (например, именованными каналами, файлами блокировки, разделяемой памятью) вам по-прежнему нужны средства для решения ситуации, когда процесс не очищался после себя, то есть при запуске теста, если файл блокировки существует, если это так, проверьте, действительно ли процесс с pid существует, а затем действуйте соответствующим образом.

Другая ситуация заключается в том, что процесс unix вызывает функцию из семейства exec, то есть после fork-exec для создания нового процесса. Здесь дочерний процесс будет иметь полную копию родительской памяти (включая объекты RAII), но после вызова exec ни один из деструкторов не будет вызван в этом процессе. С другой стороны, если процесс разветвляется и ни один из процессов не вызывает exec, все ресурсы очищаются в обоих процессах. Это правильно только для всех ресурсов, которые были фактически дублированы в fork, но с системными ресурсами оба процесса будут иметь только ссылку на ресурс (т. Е. Путь к файлу блокировки), и оба попытаются выпустить его отдельно, что потенциально может вызвать другой процесс терпит неудачу.

Блокировка

Плохая блокировка:

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.
    }
}

Это неправильный способ реализовать блокировку и разблокировку мьютекса. Для обеспечения правильной разблокировки мьютекса с помощью unlock() требуется, чтобы программатор удостоверился, что все потоки, приводящие к выходу из функции, приводят к вызову unlock() . Как показано выше, это хрупкие процессы, так как требуется, чтобы все сопровождающие продолжали следовать за рисунком вручную.

Используя подходящий класс для реализации RAII, проблема тривиальна:

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 - чрезвычайно простой шаблон класса, который просто вызывает lock() в своем аргументе в своем конструкторе, сохраняет ссылку на аргумент и вызывает unlock() в аргументе в своем деструкторе. То есть, когда lock_guard выходит из области видимости, mutex гарантированно разблокируется. Не имеет значения, является ли причина, по которой он вышел из сферы действия, исключение или раннее возвращение - все дела обрабатываются; независимо от потока управления, мы гарантировали, что мы разблокируемся правильно.

Наконец / ScopeExit

В случаях, когда мы не хотим писать специальные классы для обработки некоторого ресурса, мы можем написать общий класс:

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)}; }

И пример использования

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)`
}

Примечание (1): Некоторое обсуждение определения деструктора должно рассматриваться как обращение к исключению:

  • ~Finally() noexcept { f(); } : std::terminate вызывается в случае исключения
  • ~Finally() noexcept(noexcept(f())) { f(); } : terminate () вызывается только в случае исключения во время разматывания пакетов.
  • ~Finally() noexcept { try { f(); } catch (...) { /* ignore exception (might log it) */} } Нет std::terminate , но мы не можем обрабатывать ошибку (даже для разворачивания без стека).

ScopeSuccess (c ++ 17)

C ++ 17

Благодаря int std::uncaught_exceptions() мы можем реализовать действие, которое выполняется только при успешном завершении (исключение исключено из области). Ранее bool std::uncaught_exception() просто позволяет определить, запущена ли какая-либо переадресация стека.

#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
    }
}

Выход:

Success 1

ScopeFail (c ++ 17)

C ++ 17

Благодаря int std::uncaught_exceptions() мы можем реализовать действие, которое выполняется только при сбое (выбрано исключение в области). Ранее bool std::uncaught_exception() просто позволяет определить, запущена ли какая-либо переадресация стека.

#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;

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

Выход:

Failure 2


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow