development

C ++은 '최종'블록을 지원합니까?

big-blog 2020. 4. 2. 08:17
반응형

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


C ++은 ' finally '블록을 지원합니까 ?

RAII 관용구 는 무엇입니까 ?

C ++의 RAII 관용구와 C #의 'using'문의 차이점은 무엇입니까 ?


아니요, 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를 사용하면 멤버 리소스 수명을 수동으로 관리 할 필요가 없으므로 소유자 클래스에 대한 아주 간단한, 아마도 기본 소멸자를 사용하여 벗어날 수 있습니다. . ( 이 점을 지적 해준 Mike B 에게 감사합니다 .)

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

 

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


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


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

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

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


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

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

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

하나...

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

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


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

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

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

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


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

원하는 경우이 관용구를 "시도-마지막으로"매크로로 랩핑 할 수 있습니다.

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

    ...

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 디버깅 설정은이 메커니즘을 손상시킵니다.


다른 답변에서 지적했듯이 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을 사용하는 것이 표준화 된 기능을 사용하는 것과 비슷합니다.


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

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 블록 안에서는 사용할 수 없습니다 .


나는 자바 에서 키워드 와 거의 비슷하게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.

대체로, 내가이 물건을 직접 사용할 것인지는 모르겠지만, 그것을 가지고 노는 것은 재미있었습니다. :)


흐름 관점에서 읽기가 더 쉽다고 생각하기 때문에 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);
        }
    }

많은 사람들이 언급했듯이 해결책은 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 사용에 대한 추가 소개가 여기 있습니다.


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

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

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

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

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

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


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

참고 URL : https://stackoverflow.com/questions/161177/does-c-support-finally-blocks-and-whats-this-raii-i-keep-hearing-about

반응형