수색…


비고

RAII는 cquisition에게 I의 I의 nitialization을 esource R을 의미합니다. 때때로 SBRM (Scope-Based Resource Management) 또는 RRID (Resource Release Is Destruction)라고도하는 RAII는 리소스를 개체 수명에 연결하는 데 사용되는 관용구입니다. C ++에서 객체의 소멸자는 객체가 범위를 벗어날 때 항상 실행됩니다. 따라서 객체 정리에 리소스 정리를 묶을 수 있습니다.

최종적으로 해제해야하는 리소스 (예 : 잠금, 파일 핸들, 할당 된 버퍼)를 확보해야 할 때마다 개체를 사용하여 리소스 관리를 처리하는 것이 좋습니다. 스택 unwinding은 예외 또는 초기 스코프 종료에 관계없이 발생하므로 리소스 핸들러 객체는 가능한 모든 현재 및 미래 코드 경로를 신중하게 고려하지 않고 리소스를 정리합니다.

RAII가 자원의 수명에 대해 생각하는 개발자를 완전히 자유롭게하지는 않는다는 점은 주목할 가치가 있습니다. 한 가지 경우는 소멸자가 호출되지 않도록하는 crash 또는 exit () 호출입니다. OS는 프로세스가 끝난 후 메모리와 같은 프로세스 로컬 리소스를 정리하기 때문에 대부분의 경우 문제가되지 않습니다. 그러나 시스템 리소스 (즉, 명명 된 파이프, 잠금 파일, 공유 메모리)를 사용하면 프로세스가 자체적으로 정리하지 않은 경우 (즉, 잠금 파일이있는 경우 시작 테스트시)를 처리 할 수있는 기능이 여전히 필요합니다. pid가있는 프로세스가 실제로 존재하는지 확인한 다음 그에 따라 작동하십시오.

또 다른 상황은 유닉스 프로세스가 exec-family로부터 함수를 호출 할 때, 즉 fork-exec 후에 새로운 프로세스를 생성하는 경우이다. 여기서 자식 프로세스는 부모 메모리 (RAII 객체 포함)의 전체 사본을 가지지 만 exec가 호출되면 해당 프로세스에서 소멸자가 호출되지 않습니다. 반면에 프로세스가 분기되고 프로세스가 exec를 호출하지 않으면 두 프로세스 모두에서 모든 리소스가 정리됩니다. 이는 포크에서 실제로 복제 된 모든 자원에 대해서만 올바르지 만 시스템 자원과 함께 두 프로세스는 자원 (예 : 잠금 파일의 경로)에 대한 참조 만 가지며 개별적으로 해제하려고 시도하며 잠재적으로 다른 프로세스가 실패합니다.

잠금

잘못된 잠금 :

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

우리가 어떤 리소스를 다루기 위해 특별한 클래스를 작성하고 싶지 않을 때, 우리는 generic 클래스를 작성할 수있다 :

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