C ++은 '최종'블록을 지원합니까? (그리고 내가 계속 듣는이 'RAII'는 무엇입니까?)


답변:


273

아니요, C ++은 '최종'블록을 지원하지 않습니다. 그 이유는 C ++이 대신 RAII를 지원하기 때문입니다. "리소스 획득이 초기화입니다"- 실제로 유용한 개념으로 라는 이름이 잘못 되었습니다.

아이디어는 객체의 소멸자가 리소스 해제를 담당한다는 것입니다. 객체에 자동 저장 기간이 있으면 객체가 생성 된 블록이 종료 될 때 (예외가있는 경우 해당 블록이 종료 된 경우에도) 객체의 소멸자가 호출됩니다. 다음은 Bjarne Stroustrup의 주제에 대한 설명 입니다.

RAII의 일반적인 용도는 뮤텍스를 잠그는 것입니다.

// A class with implements RAII
class lock
{
    mutex &m_;

public:
    lock(mutex &m)
      : m_(m)
    {
        m.acquire();
    }
    ~lock()
    {
        m_.release();
    }
};

// A class which uses 'mutex' and 'lock' objects
class foo
{
    mutex mutex_; // mutex for locking 'foo' object
public:
    void bar()
    {
        lock scopeLock(mutex_); // lock object.

        foobar(); // an operation which may throw an exception

        // scopeLock will be destructed even if an exception
        // occurs, which will release the mutex and allow
        // other functions to lock the object and run.
    }
};

RAII는 객체를 다른 클래스의 멤버로 사용하는 것을 단순화합니다. 소유 클래스가 소멸되면 RAII 관리 클래스의 소멸자가 결과적으로 호출되므로 RAII 클래스가 관리하는 자원이 해제됩니다. 즉, 리소스를 관리하는 클래스의 모든 멤버에 RAII를 사용하면 멤버 리소스 수명을 수동으로 관리 할 필요가 없기 때문에 소유자 클래스에 대한 아주 간단한, 아마도 기본 소멸자를 사용하여 벗어날 수 있습니다. . ( 마이크 B 에게 감사합니다 이 점을 지적 에게 .)

C # 또는 VB.NET을 사용하는 가족의 경우 RAII가 IDisposable 및 'using'문을 사용 하는 .NET 결정 론적 파괴 와 유사하다는 것을 알 수 있습니다 . 실제로 두 방법은 매우 유사합니다. 주요 차이점은 RAII가 메모리를 포함한 모든 유형의 리소스를 결정적으로 해제한다는 것입니다. .NET에서 IDisposable을 구현할 때 (.NET 언어 C ++ / CLI조차도) 메모리를 제외하고 리소스가 결정적으로 해제됩니다. .NET에서 메모리는 결정적으로 해제되지 않습니다. 메모리는 가비지 수집주기 중에 만 해제됩니다.

 

† 일부 사람들은 "파괴는 자원 포기"가 RAII 관용구의 정확한 이름이라고 생각합니다.


18
"파괴는 자원 포기입니다"-DIRR ... 아뇨, 저에게는 효과가 없습니다. = P
Erik Forbes

14
RAII가 멈췄습니다. 실제로 변경이 없습니다. 그렇게하는 것은 어리석은 일입니다. 그러나 "자원 획득이 초기화입니다"라는 이름은 여전히 ​​좋지 않은 이름임을 인정해야합니다.
Kevin

162
SBRM == 범위 바운드 리소스 관리
Johannes Schaub-litb

10
개선 된 기술은 물론 소프트웨어를 일반적으로 엔지니어링 할 수있는 기술을 가진 사람이라면 그런 끔찍한 약어에 대한 가치있는 변명을 할 수 없습니다.
Hardryv

54
이것은 C ++ 객체의 수명과 일치하지 않는 정리 할 것이 있으면 붙어 있습니다. Lifetime Equals C ++ Class Liftime 또는 다른 방법으로 추악 해집니다 (LECCLEOEIGU?).
Warren P

79

C ++에서는 RAII 때문에 최종적으로 필요 하지 않습니다 .

RAII는 예외 안전의 책임을 객체의 사용자에서 객체의 디자이너 (및 구현 자)로 옮깁니다. 나는 디자인 / 구현에서 예외 안전을 한 번만 수정하면되기 때문에 이것이 올바른 장소라고 주장합니다. 마지막으로 사용하면 객체를 사용할 때마다 예외 안전을 올바르게 확보해야합니다.

또한 IMO 코드가 깔끔해 보입니다 (아래 참조).

예:

데이터베이스 객체. DB 연결이 사용되도록하려면 반드시 열고 닫아야합니다. RAII를 사용하면 생성자 / 소멸자에서이를 수행 할 수 있습니다.

RAII와 같은 C ++

void someFunc()
{
    DB    db("DBDesciptionString");
    // Use the db object.

} // db goes out of scope and destructor closes the connection.
  // This happens even in the presence of exceptions.

RAII를 사용하면 DB 객체를 매우 쉽게 사용할 수 있습니다. DB 객체는 우리가 시도하고 남용하는 방법에 관계없이 소멸자를 사용하여 올바르게 닫힙니다.

마지막으로 자바

void someFunc()
{
    DB      db = new DB("DBDesciptionString");
    try
    {
        // Use the db object.
    }
    finally
    {
        // Can not rely on finaliser.
        // So we must explicitly close the connection.
        try
        {
            db.close();
        }
        catch(Throwable e)
        {
           /* Ignore */
           // Make sure not to throw exception if one is already propagating.
        }
    }
}

마지막으로 사용할 때 객체의 올바른 사용이 객체의 사용자에게 위임됩니다. , DB 연결을 명시 적으로 닫는 것은 객체 사용자의 책임입니다. 이제이 작업을 파이널 라이저에서 수행 할 수 있다고 주장 할 수 있지만 리소스의 가용성 또는 기타 제약이 제한 될 수 있으므로 일반적으로 객체의 릴리스를 제어하고 가비지 수집기의 비 결정적 동작에 의존하지 않으려 고합니다.

또한 이것은 간단한 예입니다.
릴리스해야하는 리소스가 여러 개인 경우 코드가 복잡해질 수 있습니다.

더 자세한 분석은 여기에서 찾을 수 있습니다 : http://accu.org/index.php/journals/236


16
// Make sure not to throw exception if one is already propagating.이러한 이유로 C ++ 소멸자가 예외를 발생시키지 않는 것이 중요합니다.
Cemafor

10
@Cemafor : C ++이 소멸자에서 예외를 발생시키지 않는 이유는 Java와 다릅니다. Java에서는 작동합니다 (원래 예외를 풀면됩니다). C ++에서는 정말 나쁩니다. 그러나 C ++의 요점은 소멸자를 작성할 때 클래스 디자이너가 한 번만 수행하면된다는 것입니다. Java에서는 사용 시점에서해야합니다. 따라서 동일한 보일러 플레이트를 적시에 작성하는 것은 클래스 사용자의 책임입니다.
Martin York

1
"필요한"문제인 경우 RAII도 필요하지 않습니다. 그것을 제거합시다! :-) 농담을 제외하고, RAII는 많은 경우에 좋습니다. RAII가 더 성가신 것은 위의 코드가 일찍 반환 된 경우에도 리소스 관련이 아닌 일부 코드를 실행하려는 경우입니다. 이를 위해 gotos를 사용하거나 두 가지 방법으로 분리하십시오.
트리니다드

1
@ 트리니다드 : 생각하는 것처럼 간단하지 않습니다 (모든 제안이 가능한 최악의 옵션을 선택하는 것처럼 보입니다). 그렇기 때문에 질문보다 의견을 조사하는 것이 더 좋은 곳일 수 있습니다.
Martin York

1
"RAII로 인해 필요하지 않음"에 대한 비판 : 임시 RAII를 추가하기에는 너무 많은 상용구 코드가 될 수 있으며, 최종적으로 시도하는 것이 매우 적합한 경우가 많이 있습니다.
ceztko

63

RAII가 일반적으로 더 좋지만 C ++에서 최종 의미를 쉽게 가질 수 있습니다 . 적은 양의 코드를 사용합니다.

게다가 C ++ 핵심 가이드 라인이 마침내 제공됩니다.

다음은 GSL Microsoft 구현 에 대한 링크와 Martin Moene 구현에 대한 링크입니다.

Bjarne Stroustrup은 여러 번 GSL에있는 모든 것이 결국에는 표준에 들어가야한다고 말했다. 따라서 최종적 으로 사용할 미래 보장 방법이되어야합니다 .

원하는 경우 쉽게 구현하고 계속 읽을 수 있습니다.

C ++ 11에서 RAII 및 람다는 최종적으로 일반을 만들 수 있습니다.

namespace detail { //adapt to your "private" namespace
template <typename F>
struct FinalAction {
    FinalAction(F f) : clean_{f} {}
   ~FinalAction() { if(enabled_) clean_(); }
    void disable() { enabled_ = false; };
  private:
    F clean_;
    bool enabled_{true}; }; }

template <typename F>
detail::FinalAction<F> finally(F f) {
    return detail::FinalAction<F>(f); }

사용 예 :

#include <iostream>
int main() {
    int* a = new int;
    auto delete_a = finally([a] { delete a; std::cout << "leaving the block, deleting a!\n"; });
    std::cout << "doing something ...\n"; }

출력은 다음과 같습니다.

doing something...
leaving the block, deleting a!

개인적으로 C ++ 프로그램에서 POSIX 파일 디스크립터를 닫는 데 몇 번 사용했습니다.

자원을 관리하는 등 누출 어떤 종류의 피할 수 진짜 클래스를 갖는 것이 더 좋습니다,하지만이 마침내 잔인한 같은 수준의 소리를 만드는 경우에 유용합니다.

게다가 자연스럽게 사용하면 시작 코드 근처에 닫는 코드를 작성하고 (예제에서는 newdelete ) C ++에서 평소대로 LIFO 순서대로 생성이 이루어 지므로 마지막으로 다른 언어보다 좋습니다 . 유일한 단점은 실제로 사용하지 않는 자동 변수를 얻고 람다 구문이 약간 시끄럽게 만든다는 것입니다 (네 번째 예제에서 마지막 단어 만 오른쪽에 {} 블록은 의미가 있습니다. 휴식은 본질적으로 소음입니다).

다른 예시:

 [...]
 auto precision = std::cout.precision();
 auto set_precision_back = finally( [precision, &std::cout]() { std::cout << std::setprecision(precision); } );
 std::cout << std::setprecision(3);

비활성화 (가) 경우 회원은 유용하다 결국 유일한 실패의 경우에 호출 할 수있다. 예를 들어, 세 개의 다른 컨테이너에 객체를 복사해야합니다. 최종적 으로 각 복사를 실행 취소하고 모든 복사에 성공한 후 비활성화 할 수 있습니다 . 그렇게하면 파괴가되지 않으면 강력한 보증을받을 수 있습니다.

비활성화 :

//strong guarantee
void copy_to_all(BIGobj const& a) {
    first_.push_back(a);
    auto undo_first_push = finally([first_&] { first_.pop_back(); });

    second_.push_back(a);
    auto undo_second_push = finally([second_&] { second_.pop_back(); });

    third_.push_back(a);
    //no necessary, put just to make easier to add containers in the future
    auto undo_third_push = finally([third_&] { third_.pop_back(); });

    undo_first_push.disable();
    undo_second_push.disable();
    undo_third_push.disable(); }

C ++ 11을 사용할 수 없다면 여전히 finally를 가질 수 있지만 코드가 약간 길어집니다. 생성자와 소멸자만으로 구조체를 정의하면 생성자가 필요한 것을 참조하고 소멸자는 필요한 작업을 수행합니다. 기본적으로 람다는 수동으로 수행됩니다.

#include <iostream>
int main() {
    int* a = new int;

    struct Delete_a_t {
        Delete_a_t(int* p) : p_(p) {}
       ~Delete_a_t() { delete p_; std::cout << "leaving the block, deleting a!\n"; }
        int* p_;
    } delete_a(a);

    std::cout << "doing something ...\n"; }

가능한 문제가있을 수 있습니다 : 'finally (F f)'함수에서 FinalAction의 객체를 반환하므로 최종적으로 함수를 반환하기 전에 해체자가 호출 될 수 있습니다. 템플릿 F 대신 std :: function을 사용해야 할 수도 있습니다.
user1633272

참고 FinalAction기본적으로 대중과 동일한 ScopeGuard경우에만 다른 이름으로, 관용구.
anderas

1
이 최적화는 안전합니까?
Nulano

2
@ Paolo.Bolzoni 더 빨리 답장을 보내지 않아서 죄송합니다. 귀하의 의견에 대한 알림을받지 못했습니다. 변수가 사용되지 않기 때문에 스코프가 끝나기 전에 finally 블록 (DLL 함수를 호출하는)이 호출 될까 걱정했지만 SO에 대한 질문을 찾았습니다. 나는 그것에 링크 할 것이지만 불행히도 더 이상 찾을 수 없습니다.
Nulano

1
disable () 함수는 깔끔한 디자인에 약간의 경고입니다. 실패한 경우에만 finally를 호출하려면 catch 문을 사용하지 않는 이유는 무엇입니까? 그게 아닌가?
user2445507

32

RAII는 스택 기반 객체로 쉽게 정리할 수있을뿐만 아니라 객체가 다른 클래스의 구성원 일 때 동일한 '자동'정리가 수행되므로 유용합니다. 소유 클래스가 소멸되면 해당 클래스의 dtor가 결과적으로 호출되므로 RAII 클래스가 관리하는 자원이 정리됩니다.

즉, RAII 열반에 도달하고 클래스의 모든 구성원이 스마트 포인터와 같은 RAII를 사용하면 소유자 클래스에 대해 매우 간단한 (아마도 기본) dtor를 사용하여 수동으로 관리 할 필요가 없기 때문에 벗어날 수 있습니다. 회원 자원 수명.


아주 좋은 지적입니다. 당신에게 +1. 다른 많은 사람들이 당신을 투표하지 않았습니다. 귀하의 의견을 포함하도록 내 게시물을 편집 한 것을 신경 쓰지 않기를 바랍니다. (물론 크레딧을주었습니다.) 감사합니다! :)
Kevin

30

가비지 수집기에 의해 리소스가 자동으로 할당 해제 되더라도 관리되는 언어조차도 최종 차단을 제공하는 이유는 무엇입니까?

실제로 가비지 콜렉터를 기반으로하는 언어에는 "최종"이 더 필요합니다. 가비지 수집기는 적시에 개체를 파괴하지 않으므로 메모리와 관련되지 않은 문제를 올바르게 정리하는 데 의존 할 수 없습니다.

동적으로 할당 된 데이터와 관련하여 많은 사람들이 스마트 포인터를 사용해야한다고 주장합니다.

하나...

RAII는 예외 안전의 책임을 객체 사용자에서 디자이너로 옮깁니다.

슬프게도 이것은 그 자체의 몰락입니다. 오래된 C 프로그래밍 습관은 열심히 죽습니다. C 또는 매우 C 스타일로 작성된 라이브러리를 사용하는 경우 RAII는 사용되지 않습니다. 전체 API 프런트 엔드를 다시 작성하는 것이 부족하기 때문에 작업해야합니다. 그렇다면 "마침내"의 부족은 실제로 물린다.


13
정확히 ... RAII는 이상적인 관점에서 좋은 것 같습니다. 그러나 Win32 API의 C 스타일 함수와 같이 항상 기존 C API로 작업해야합니다. 일종의 HANDLE을 반환하는 리소스를 얻는 것이 매우 일반적이며 정리를 위해서는 CloseHandle (HANDLE)과 같은 함수가 필요합니다. try ...를 사용하면 가능한 예외를 처리하는 좋은 방법입니다. (고맙게도 사용자 정의 삭제 기가있는 shared_ptr처럼 보이고 C ++ 11 람다는 RAII 기반 릴리프를 제공해야합니다.이 클래스는 전체 클래스를 작성하지 않아도되어 한 곳에서만 사용하는 일부 API를 래핑해야합니다.)
James Johnston

7
@JamesJohnston, 모든 종류의 핸들을 보유하고 RAII 메커니즘을 제공하는 래퍼 클래스를 작성하는 것은 매우 쉽습니다. ATL은 예를 들어 많은 것을 제공합니다. 당신이 이것을 너무 많은 문제로 생각하는 것 같지만, 나는 동의하지 않습니다.
Mark Ransom

5
간단합니다. 작습니다. 크기는 작업중인 라이브러리의 복잡성에 따라 다릅니다.
Philip Couling

1
@MarkRansom : 정리 중에 예외가 발생하고 다른 예외가 보류중인 경우 RAII가 지능적으로 무언가를 수행 할 수있는 메커니즘이 있습니까? try / finally가있는 시스템에서는 보류 중 예외와 정리 중에 발생한 예외가 모두 새로 저장되도록 항목을 정렬하는 것이 가능합니다 (어색하지만) CleanupFailedException. RAII를 사용하여 이러한 결과를 달성 할 수있는 적절한 방법이 있습니까?
supercat

3
@couling : 프로그램이 SomeObject.DoSomething()메소드 를 호출하고 메소드가 (1) 성공했는지, (2) 부작용없이 실패했는지 , (3) 호출자가 대처할 준비가 된 부작용으로 실패 했는지를 알고 싶어하는 경우가 많이 있습니다. 또는 (4) 발신자가 처리 할 수없는 부작용으로 실패했습니다. 발신자 만이 어떤 상황에 대처할 수 있고 대처할 수 없는지 알게됩니다. 발신자가 필요로하는 것은 상황이 무엇인지 아는 방법입니다. 예외에 관한 가장 중요한 정보를 제공하기위한 표준 메커니즘이 없다는 것은 너무 나쁩니다.
supercat December

9

C ++ 11 람다 함수를 사용하는 또 다른 "최종"블록 에뮬레이션

template <typename TCode, typename TFinallyCode>
inline void with_finally(const TCode &code, const TFinallyCode &finally_code)
{
    try
    {
        code();
    }
    catch (...)
    {
        try
        {
            finally_code();
        }
        catch (...) // Maybe stupid check that finally_code mustn't throw.
        {
            std::terminate();
        }
        throw;
    }
    finally_code();
}

컴파일러가 위의 코드를 최적화하기를 바랍니다.

이제 다음과 같은 코드를 작성할 수 있습니다.

with_finally(
    [&]()
    {
        try
        {
            // Doing some stuff that may throw an exception
        }
        catch (const exception1 &)
        {
            // Handling first class of exceptions
        }
        catch (const exception2 &)
        {
            // Handling another class of exceptions
        }
        // Some classes of exceptions can be still unhandled
    },
    [&]() // finally
    {
        // This code will be executed in all three cases:
        //   1) exception was not thrown at all
        //   2) exception was handled by one of the "catch" blocks above
        //   3) exception was not handled by any of the "catch" block above
    }
);

이 관용구를 "try-finally"매크로로 묶을 수 있습니다.

// Please never throw exception below. It is needed to avoid a compilation error
// in the case when we use "begin_try ... finally" without any "catch" block.
class never_thrown_exception {};

#define begin_try    with_finally([&](){ try
#define finally      catch(never_thrown_exception){throw;} },[&]()
#define end_try      ) // sorry for "pascalish" style :(

이제 C ++ 11에서 "finally"블록을 사용할 수 있습니다 :

begin_try
{
    // A code that may throw
}
catch (const some_exception &)
{
    // Handling some exceptions
}
finally
{
    // A code that is always executed
}
end_try; // Sorry again for this ugly thing

개인적으로 저는 "최종"관용구의 "매크로"버전이 마음에 들지 않으며 구문이 더 큰 경우에도 순수한 "with_finally"함수를 사용하는 것을 선호합니다.

여기에서 위의 코드를 테스트 할 수 있습니다 : http://coliru.stacked-crooked.com/a/1d88f64cb27b3813

추신

코드에 finally 블록 이 필요한 경우 범위 가드 또는 ON_FINALLY / ON_EXCEPTION 매크로가 사용자의 요구에 더 잘 맞을 것입니다.

다음은 ON_FINALLY / ON_EXCEPTION 사용법의 간단한 예입니다.

void function(std::vector<const char*> &vector)
{
    int *arr1 = (int*)malloc(800*sizeof(int));
    if (!arr1) { throw "cannot malloc arr1"; }
    ON_FINALLY({ free(arr1); });

    int *arr2 = (int*)malloc(900*sizeof(int));
    if (!arr2) { throw "cannot malloc arr2"; }
    ON_FINALLY({ free(arr2); });

    vector.push_back("good");
    ON_EXCEPTION({ vector.pop_back(); });

    ...

1
첫 번째는이 페이지에 제시된 모든 옵션 중에서 가장 읽기 쉬운 것입니다. +1
Nikos

7

오래된 스레드를 파서 죄송하지만 다음과 같은 이유로 큰 오류가 있습니다.

RAII는 예외 안전의 책임을 객체의 사용자에서 객체의 디자이너 (및 구현 자)로 옮깁니다. 나는 디자인 / 구현에서 예외 안전을 한 번만 수정하면되기 때문에 이것이 올바른 장소라고 주장합니다. 마지막으로 사용하면 객체를 사용할 때마다 예외 안전을 올바르게 확보해야합니다.

try-block 내에서 일부 코드는 많은 객체를 생성하고 (런타임에 몇 개가 결정되는지) 목록에 객체에 대한 포인터를 저장할 수 있습니다. 이제 이것은 이국적인 시나리오가 아니라 매우 일반적입니다. 이 경우 다음과 같은 내용을 작성하고 싶습니다.

void DoStuff(vector<string> input)
{
  list<Foo*> myList;

  try
  {    
    for (int i = 0; i < input.size(); ++i)
    {
      Foo* tmp = new Foo(input[i]);
      if (!tmp)
        throw;

      myList.push_back(tmp);
    }

    DoSomeStuff(myList);
  }
  finally
  {
    while (!myList.empty())
    {
      delete myList.back();
      myList.pop_back();
    }
  }
}

물론 범위를 벗어나면 목록 자체가 삭제되지만 생성 한 임시 개체는 정리되지 않습니다.

대신, 추악한 길을 가야합니다.

void DoStuff(vector<string> input)
{
  list<Foo*> myList;

  try
  {    
    for (int i = 0; i < input.size(); ++i)
    {
      Foo* tmp = new Foo(input[i]);
      if (!tmp)
        throw;

      myList.push_back(tmp);
    }

    DoSomeStuff(myList);
  }
  catch(...)
  {
  }

  while (!myList.empty())
  {
    delete myList.back();
    myList.pop_back();
  }
}

또한 가비지 수집기에서 리소스를 자동으로 할당 해제 했음에도 불구하고 관리되는 세탁소조차도 최종 차단을 제공하는 이유는 무엇입니까?

힌트 : 단순히 메모리 할당 해제보다 "최종"으로 더 많은 일을 할 수 있습니다.


17
관리되는 언어는 메모리라는 한 종류의 리소스 만 자동으로 관리되므로 최종적으로 정확하게 차단해야합니다. RAII는 모든 리소스를 동일한 방식으로 처리 할 수 ​​있으므로 최종적으로 필요하지 않습니다. 예제에서 실제로 RAII 대신 스마트 포인터를 사용하여 RAII를 사용한 경우 코드는 "최종"예제보다 간단합니다. new의 반환 값을 확인하지 않으면 훨씬 간단합니다. 확인하는 것은 무의미합니다.
Myto

7
newNULL을 반환하지 않고 대신 예외를 던집니다
Hasturkun

5
중요한 질문을 제기하지만 두 가지 가능한 답변이 있습니다. 하나는 Myto가 제공 한 것입니다. 모든 동적 할당에 스마트 포인터를 사용하십시오. 다른 하나는 표준 컨테이너를 사용하는 것입니다. 표준 컨테이너는 파괴시 항상 내용물을 파괴합니다. 어느 쪽이든, 할당 된 모든 객체는 궁극적으로 객체가 자동으로 해제되는 정적으로 할당 된 객체에 의해 소유됩니다. 프로그래머가 평범한 포인터와 배열의 높은 가시성으로 인해 이러한 더 나은 솔루션을 찾기가 어렵다는 것은 부끄러운 일입니다.
j_random_hacker 2018 년

4
C ++ 11이 향상되고 포함 std::shared_ptr하고 std::unique_ptr바로 다음 stdlib있다.
u0b34a0f6ae

16
당신의 예가 너무 끔찍한 이유는 RAII에 결함이 있기 때문이 아니라 그것을 사용하지 않았기 때문입니다. 원시 포인터는 RAII가 아닙니다.
Ben Voigt

6

FWIW, Microsoft Visual C ++는 try, finally를 지원하며, 과거에는 MFC 응용 프로그램에서 충돌을 유발하는 심각한 예외를 포착하는 방법으로 사용되어 왔습니다. 예를 들어;

int CMyApp::Run() 
{
    __try
    {
        int i = CWinApp::Run();
        m_Exitok = MAGIC_EXIT_NO;
        return i;
    }
    __finally
    {
        if (m_Exitok != MAGIC_EXIT_NO)
            FaultHandler();
    }
}

과거에는 이것을 사용하여 종료하기 전에 열린 파일의 백업 저장과 같은 작업을 수행했습니다. 특정 JIT 디버깅 설정은이 메커니즘을 손상시킵니다.


4
실제로 C ++ 예외는 아니지만 SEH 예외입니다. MS C ++ 코드에서 둘 다 사용할 수 있습니다. SEH는 VB, .NET이 예외를 구현하는 방식 인 OS 예외 처리기입니다.
gbjbaanb

그리고 SetUnhandledExceptionHandler를 사용하여 SEH 예외에 대해 '전역 적'캐치되지 않은 예외 핸들러를 작성할 수 있습니다.
gbjbaanb

3
SEH는 끔찍하며 C ++ 소멸자가 호출되는 것을 방지합니다.
paulm

6

다른 답변에서 지적했듯이 C ++은 finally유사한 기능을 지원할 수 있습니다 . 표준 언어의 일부에 가장 가까운이 기능의 구현은 Bjarne Stoustrup 및 Herb Sutter에 의해 편집 된 C ++ 사용을위한 모범 사례 세트 인 C ++ 핵심 가이드 라인 과 함께 제공되는 것 입니다. 의 구현finally 의 일부 가이드 라인 지원 라이브러리 (GSL). 가이드 라인 전체에서 finally구식 인터페이스를 처리 할 때는 사용 이 권장되며, 적절한 리소스 핸들이없는 경우 final_action 객체를 사용하여 정리를 표현합니다. 라는 지침 이 있습니다 .

따라서 C ++ 지원뿐만 아니라 finally많은 일반적인 사용 사례에서 실제로 사용하는 것이 좋습니다.

GSL 구현의 사용 예는 다음과 같습니다.

#include <gsl/gsl_util.h>

void example()
{
    int handle = get_some_resource();
    auto handle_clean = gsl::finally([&handle] { clean_that_resource(handle); });

    // Do a lot of stuff, return early and throw exceptions.
    // clean_that_resource will always get called.
}

GSL 구현 및 사용법은 Paolo.Bolzoni의 답변 과 매우 유사합니다 . 한 가지 차이점은 생성 된 객체 gsl::finally()disable()호출 이 없다는 것 입니다. 해당 기능이 필요한 경우 (예를 들어, 리소스가 일단 조립되고 예외가 발생하지 않는 경우 반환) Paolo의 구현을 선호 할 수 있습니다. 그렇지 않으면 GSL을 사용하는 것이 표준화 된 기능을 사용하는 것과 비슷합니다.


3

실제로는 아니지만 다음과 같이 확장 할 수 있습니다.

int * array = new int[10000000];
try {
  // Some code that can throw exceptions
  // ...
  throw std::exception();
  // ...
} catch (...) {
  // The finally-block (if an exception is thrown)
  delete[] array;
  // re-throw the exception.
  throw; 
}
// The finally-block (if no exception was thrown)
delete[] array;

finally 블록 자체는 원래 예외가 다시 발생하기 전에 예외를 throw하여 원래 예외를 버릴 수 있습니다. 이것은 Java finally 블록에서와 동일한 동작입니다. 또한 returntry & catch 블록 안에서는 사용할 수 없습니다 .


3
finally 블록이 발생할 수 있다고 언급하게되어 기쁩니다. 대부분의 "RAII 사용"답변은 무시하는 것 같습니다. 마지막으로 두 번 차단하려면 작성하지 않도록하려면, 당신은 같은 것을 할 수std::exception_ptr e; try { /*try block*/ } catch (...) { e = std::current_exception(); } /*finally block*/ if (e) std::rethrow_exception(e);
sethobrien

1
그게 내가 알고 싶은 전부 야! 왜 다른 답변들도 catch (...) + empty throw; 거의 finally 블록처럼 작동합니까? 때때로 당신은 그것을 필요로합니다.
VinGarcia

내 대답 ( stackoverflow.com/a/38701485/566849 ) 에서 제공 한 솔루션 은 finally블록 내부에서 예외를 던질 수 있어야합니다 .
Fabio A.

3

나는 자바 에서 키워드 와 거의 비슷하게finally 사용될 수 있는 매크로를 생각 해냈다 finally. 그것은 사용하게 std::exception_ptr친구, 람다 함수와 std::promise이 필요하므로, C++11위 또는; 또한 복합 명령문 표현식을 사용합니다. clang에서 지원 GCC 확장 .

경고 : 이 답변 의 이전 버전 은 더 많은 제한이있는 다른 개념 구현을 사용했습니다.

먼저 도우미 클래스를 정의 해 봅시다.

#include <future>

template <typename Fun>
class FinallyHelper {
    template <typename T> struct TypeWrapper {};
    using Return = typename std::result_of<Fun()>::type;

public:    
    FinallyHelper(Fun body) {
        try {
            execute(TypeWrapper<Return>(), body);
        }
        catch(...) {
            m_promise.set_exception(std::current_exception());
        }
    }

    Return get() {
        return m_promise.get_future().get();
    }

private:
    template <typename T>
    void execute(T, Fun body) {
        m_promise.set_value(body());
    }

    void execute(TypeWrapper<void>, Fun body) {
        body();
    }

    std::promise<Return> m_promise;
};

template <typename Fun>
FinallyHelper<Fun> make_finally_helper(Fun body) {
    return FinallyHelper<Fun>(body);
}

그런 다음 실제 매크로가 있습니다.

#define try_with_finally for(auto __finally_helper = make_finally_helper([&] { try 
#define finally });                         \
        true;                               \
        ({return __finally_helper.get();})) \
/***/

다음과 같이 사용할 수 있습니다 :

void test() {
    try_with_finally {
        raise_exception();
    }    

    catch(const my_exception1&) {
        /*...*/
    }

    catch(const my_exception2&) {
        /*...*/
    }

    finally {
        clean_it_all_up();
    }    
}

를 사용 std::promise하면 구현하기가 매우 쉬워 지지만에서 필요한 기능 만 다시 구현하면 피할 수있는 불필요한 오버 헤드가 발생할 수 있습니다 std::promise.


¹주의 사항 : 의 자바 버전처럼 작동하지 않는 몇 가지가 finally있습니다. 내 머리 꼭대기에서 :

  1. 및 블록 break내에서 명령문을 사용하여 외부 루프에서 벗어날 수 없습니다. 이들은 람다 함수 내에 있기 때문입니다.trycatch()
  2. 다음에 최소한 하나의 catch()블록 이 있어야합니다 try. C ++ 요구 사항입니다.
  3. 함수에 void 이외의 반환 값이 있지만 tryand catch()'s블록 내에 반환이 없으면 finally매크로가를 반환하려는 코드로 확장 되므로 컴파일이 실패 합니다 void. 이것은 일종의 매크로를 가짐으로써 무효 가 될 수 있습니다 finally_noreturn.

전체적으로, 나는 내가이 물건을 직접 사용할 것인지 모르겠지만, 그것과 함께 노는 것은 재미있었습니다. :)


그래, 그것은 단지 빠른 해킹 이었지만 프로그래머가 그들이하는 일을 알고 있다면 그럼에도 유용 할 수 있습니다.
Fabio A.

@ MarkLakata, 예외 및 반환 던지기를 지원하는 더 나은 구현으로 게시물을 업데이트했습니다.
Fabio A.

좋아 보인다 매크로 catch(xxx) {}시작 부분에 불가능한 블록을 넣으면 Caveat 2를 제거 할 수 있습니다 finally. 여기서 xxx는 최소한 하나의 catch 블록을 갖는 목적으로 가짜 유형입니다.
Mark Lakata

@ MarkLakata, 나도 그렇게 생각했지만 사용하기가 불가능 catch(...)하지 않습니까?
Fabio A.

나는 그렇게 생각하지 않습니다. xxx절대 사용되지 않는 개인 네임 스페이스에서 모호한 유형 을 구성하십시오.
Mark Lakata

2

흐름 관점에서 읽기가 더 쉽다고 생각하기 때문에 C ++ 11 언어에서 완벽하게 수용 가능한 부분 finally 이어야 한다고 생각하는 유스 케이스 가 있습니다. 내 유스 케이스는 소비자 / 생산자 스레드 체인 nullptr으로, 실행 종료시 센티넬 이 전송되어 모든 스레드를 종료합니다.

C ++에서 지원한다면 코드는 다음과 같습니다.

    extern Queue downstream, upstream;

    int Example()
    {
        try
        {
           while(!ExitRequested())
           {
             X* x = upstream.pop();
             if (!x) break;
             x->doSomething();
             downstream.push(x);
           } 
        }
        finally { 
            downstream.push(nullptr);
        }
    }

루프가 종료 된 후에 발생하기 때문에 마지막 선언을 루프 시작 부분에 배치하는 것이 더 논리적이라고 생각합니다 ...하지만 C ++에서는이를 수행 할 수 없기 때문에 바람직한 생각입니다. 대기열 downstream이 다른 스레드에 연결되어 있으므로이 시점에서 파괴 할 수 없기 때문에 push(nullptr)소멸자에 센티넬 을 넣을 downstream수 없습니다 ... 다른 스레드가를받을 때까지 살아 있어야합니다 nullptr.

람다와 함께 RAII 클래스를 사용하여 동일한 작업을 수행하는 방법은 다음과 같습니다.

    class Finally
    {
    public:

        Finally(std::function<void(void)> callback) : callback_(callback)
        {
        }
        ~Finally()
        {
            callback_();
        }
        std::function<void(void)> callback_;
    };

사용 방법은 다음과 같습니다.

    extern Queue downstream, upstream;

    int Example()
    {
        Finally atEnd([](){ 
           downstream.push(nullptr);
        });
        while(!ExitRequested())
        {
           X* x = upstream.pop();
           if (!x) break;
           x->doSomething();
           downstream.push(x);
        }
    }

안녕하세요, 위의 답변 ( stackoverflow.com/a/38701485/566849 )이 귀하의 요구 사항을 완전히 충족 한다고 생각 합니다.
Fabio A.

1

많은 사람들이 언급했듯이 해결책은 finally 블록을 피하기 위해 C ++ 11 기능을 사용하는 것입니다. 기능 중 하나는 unique_ptr입니다.

다음은 RAII 패턴을 사용하여 작성된 Mephane의 답변입니다.

#include <vector>
#include <memory>
#include <list>
using namespace std;

class Foo
{
 ...
};

void DoStuff(vector<string> input)
{
    list<unique_ptr<Foo> > myList;

    for (int i = 0; i < input.size(); ++i)
    {
      myList.push_back(unique_ptr<Foo>(new Foo(input[i])));
    }

    DoSomeStuff(myList);
}

C ++ 표준 라이브러리 컨테이너와 함께 unique_ptr 사용에 대한 추가 소개가 여기 있습니다.


0

대안을 제공하고 싶습니다.

finally 블록을 항상 호출하려면 마지막 catch 블록 뒤에 넣으십시오 (아마도 catch( ... )알려진 예외를 포착 해야 함)

try{
   // something that might throw exception
} catch( ... ){
   // what to do with uknown exception
}

//final code to be called always,
//don't forget that it might throw some exception too
doSomeCleanUp(); 

예외가 발생했을 때 마지막으로 블록을 마지막으로 수행하려면 부울 로컬 변수를 사용할 수 있습니다. 값:

bool generalAppState = false;
try{
   // something that might throw exception

   //the very end of try block:
   generalAppState = true;
} catch( ... ){
   // what to do with uknown exception
}

//final code to be called only when exception was thrown,
//don't forget that it might throw some exception too
if( !generalAppState ){
   doSomeCleanUpOfDirtyEnd();
}

//final code to be called only when no exception is thrown
//don't forget that it might throw some exception too
else{
   cleanEnd();
}

finally 블록의 요점은 코드 에서 예외가 코드 블록을 벗어나도록 허용 해야하는 경우에도 정리를 수행하는 것이기 때문에 작동하지 않습니다 . `try {// 아마도 "B"를 던질 수있는 것들} catch (A & a) {} finally {// 만약 C ++이 그것을 가지고 있다면 ... // "B"가 던져 지더라도 반드시 일어나야 할 것들. } // "B"가 발생하면 실행되지 않습니다. `IMHO의 예외는 오류 처리 코드 를 줄이는 것이므로, 던지기가 발생할 수있는 어획량 블록은 비생산적입니다. 이것이 RAII가 도움이되는 이유입니다. 자유로이 적용되는 경우 예외는 최상위 계층과 하위 계층에서 가장 중요합니다.
burlyearly

1
@burlyearly 당신의 의견이 거룩하지는 않지만 요점을 얻지 만 C ++에서는 그런 것이 없으므로이 동작을 에뮬레이트하는 최상위 계층으로 고려해야합니다.
jave.web

DOWNVOTE = PLEASE COMMENT :)
jave.web

0

또한 RIIA는 예외 처리와 최종 처리를 완전히 대체하지는 않는다고 생각합니다. BTW, 나는 또한 RIIA가 도처에 나쁜 이름이라고 생각합니다. 이러한 유형의 클래스를 '관리자'라고 부르고 많이 사용합니다. 시간의 95 %는 자원을 초기화하거나 획득하지 않고, 범위에 따라 일부 변경 사항을 적용하거나, 이미 설정된 것을 취해 파괴되었는지 확인합니다. 이것은 인터넷에 집착하는 공식적인 패턴 이름이기 때문에 내 이름이 더 나을 수도 있다는 제안으로 인해 학대 당합니다.

나는 여러 가지를 잡을 필요가있을 때 모든 백업을 정리할 때 합병증을 피하기 위해 일부 임시 항목 목록의 모든 복잡한 설정에 포함 할 클래스가 있어야한다고 생각하는 것이 합리적이라고 생각하지 않습니다. 프로세스에서 문제가 발생하면 예외 유형. 이것은 그렇지 않으면 필요하지 않은 많은 특별 클래스로 이어질 것입니다.

예. 특정 리소스를 관리하도록 설계된 클래스 나 비슷한 리소스 집합을 처리하도록 설계된 일반 클래스에는 적합합니다. 그러나 관련된 모든 것들이 그러한 래퍼를 가지고 있더라도 정리의 조정은 소멸자를 역순으로 간단하게 호출하는 것이 아닙니다.

C ++이 마침내 갖는 것이 합리적이라고 생각합니다. 제즈, 지난 수십 년 동안 너무 많은 비트와 밥이 붙어있어 이상한 사람들이 갑자기 유용 할 수있는 것처럼 보전 될 것 같았습니다. 아마도 다른 것들만큼 복잡하지 않을 것입니다. 추가되었지만 (내 생각에는 추측 일뿐입니다.)


-2
try
{
  ...
  goto finally;
}
catch(...)
{
  ...
  goto finally;
}
finally:
{
  ...
}

35
귀여운 관용구이지만 그다지 동일하지는 않습니다. try 블록 또는 catch로 돌아 오는 것은 'finally :'코드를 통과하지 않습니다.
Edward KMETT

10
Edward Kmett가 매우 중요한 차이점을 제시하므로이 오답을 0 점으로 유지하는 것이 좋습니다.
Mark Lakata

12
더 큰 결함 (IMO) :이 코드는 모든 예외를 처리 finally하지만 그렇지 않습니다.
벤 Voigt
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.