문제

따라서 C++에서 예외를 중첩하는 방법은 다음과 같습니다. std::nested_exception 이다:

void foo() {
  try {
    // code that might throw
    std::ifstream file("nonexistent.file");
    file.exceptions(std::ios_base::failbit);
  }

  catch(...) {
    std::throw_with_nested(std::runtime_error("foo failed"));
  }
}

그러나 이 기술은 예외를 중첩하려는 모든 수준에서 명시적인 try/catch 블록을 사용하는데, 이는 아무리 말해도 보기 흉합니다.

RAII, 이는 존 칼브(Jon Kalb) 확장 "책임 획득은 초기화"이므로 명시적인 try/catch 블록을 사용하는 대신 예외를 처리하는 훨씬 더 깔끔한 방법입니다.RAII를 사용하면 명시적인 try/catch 블록은 궁극적으로 예외를 처리하는 데만 주로 사용됩니다.사용자에게 오류 메시지를 표시하기 위해.

위의 코드를 보면 다음과 같이 입력되는 것 같습니다. foo() 예외사항을 다음과 같이 보고할 책임이 수반되는 것으로 볼 수 있습니다. std::runtime_error("foo failed") Nested_Exception 안에 세부정보를 중첩합니다.RAII를 사용하여 이 책임을 획득할 수 있다면 코드는 훨씬 더 깔끔해 보입니다.

void foo() {
  Throw_with_nested on_error("foo failed");

  // code that might throw
  std::ifstream file("nonexistent.file");
  file.exceptions(std::ios_base::failbit);
}

여기서 RAII 구문을 사용하여 명시적인 try/catch 블록을 대체할 수 있는 방법이 있습니까?


이를 수행하려면 소멸자가 호출될 때 소멸자 호출이 예외로 인한 것인지 확인하고, 그렇다면 해당 예외를 중첩하고, 해제가 정상적으로 계속되도록 새로운 중첩 예외를 발생시키는 형식이 필요합니다.다음과 같이 보일 수 있습니다.

struct Throw_with_nested {
  const char *msg;

  Throw_with_nested(const char *error_message) : msg(error_message) {}

  ~Throw_with_nested() {
    if (std::uncaught_exception()) {
      std::throw_with_nested(std::runtime_error(msg));
    }
  }
};

하지만 std::throw_with_nested() '현재 처리된 예외'가 활성화되어야 합니다. 즉, catch 블록의 컨텍스트 내부를 제외하고는 작동하지 않습니다.따라서 다음과 같은 것이 필요합니다.

  ~Throw_with_nested() {
    if (std::uncaught_exception()) {
      try {
        rethrow_uncaught_exception();
      }
      catch(...) {
        std::throw_with_nested(std::runtime_error(msg));
      }
    }
  }

불행하게도 내가 아는 한 그런 건 없다. rethrow_uncaught_excpetion() C++에 정의되어 있습니다.

도움이 되었습니까?

해결책

소멸자에서 포착되지 않은 예외를 포착(및 소비)하는 메서드가 없으면 중첩 여부에 관계없이 소멸자의 컨텍스트에서 예외를 다시 발생시킬 수 있는 방법이 없습니다. std::terminate 호출됩니다(예외 처리 컨텍스트에서 예외가 발생하는 경우).

std::current_exception (와 결합 std::rethrow_exception)는 현재 처리된 예외에 대한 포인터만 반환합니다.이 경우 예외는 명시적으로 처리되지 않으므로 이 시나리오에서는 해당 사용이 불가능합니다.

위의 내용을 고려할 때, 제공할 수 있는 유일한 대답은 미학적 관점입니다.함수 수준의 try 블록을 사용하면 보기가 약간 덜 흉해집니다.(원하는 스타일에 맞게 조정하세요):

void foo() try {
  // code that might throw
  std::ifstream file("nonexistent.file");
  file.exceptions(std::ios_base::failbit);
}
catch(...) {
  std::throw_with_nested(std::runtime_error("foo failed"));
}

다른 팁

RAII로는 불가능해요

간단한 규칙을 고려하면

소멸자는 절대로 던져서는 안됩니다.

RAII로는 원하는 것을 구현하는 것이 불가능합니다.이 규칙에는 한 가지 간단한 이유가 있습니다.비행 중 예외로 인해 스택 해제 중에 소멸자가 예외를 throw하는 경우 terminate() 호출되고 애플리케이션이 종료됩니다.

대안

C++11에서는 삶을 좀 더 쉽게 만들어주는 람다로 작업할 수 있습니다.당신은 쓸 수 있습니다

void foo()
{
    giveErrorContextOnFailure( "foo failed", [&]
    {
        // code that might throw
        std::ifstream file("nonexistent.file");
        file.exceptions(std::ios_base::failbit);
    } );
}

함수를 구현하면 giveErrorContextOnFailure 다음과 같은 방법으로:

template <typename F>
auto giveErrorContextOnFailure( const char * msg, F && f ) -> decltype(f())
{
    try { return f(); }
    catch { std::throw_with_nested(std::runtime_error(msg)); }
}

여기에는 몇 가지 장점이 있습니다.

  • 오류가 중첩되는 방식을 캡슐화합니다.
  • 이 기술을 프로그램 전체에서 엄격하게 준수한다면 오류가 중첩되는 방식을 변경하는 것은 전체 프로그램에 대해 변경될 수 있습니다.
  • 오류 메시지는 RAII에서와 마찬가지로 코드 앞에 기록될 수 있습니다.이 기술은 중첩된 범위에도 사용할 수 있습니다.
  • 코드 반복이 줄어듭니다.당신은 쓸 필요가 없습니다 try, catch, std::throw_with_nested 그리고 std::runtime_error.이렇게 하면 코드를 더 쉽게 유지 관리할 수 있습니다.프로그램의 동작을 변경하려면 한 곳에서만 코드를 변경해야 합니다.
  • 반환 유형은 자동으로 추론됩니다.그래서 당신의 기능이라면 foo() 뭔가를 반환해야 한다면 그냥 추가하면 됩니다 return ~ 전에 giveErrorContextOnFailure 함수 foo()에서.

릴리스 모드에서는 템플릿이 기본적으로 인라인되므로 일반적으로 try-catch-way 작업에 비해 성능 패널티가 없습니다.

따라야 할 또 하나의 흥미로운 규칙은 다음과 같습니다.

사용하지 마세요 std::uncaught_exception().

좋은 게 있어요 이것에 관한 기사 이 규칙을 완벽하게 설명하는 Herb Sutter의 주제입니다.간단히 말해서:기능이 있는 경우 f() 라고 불리는 스택 해제 중 소멸자 내에서 이렇게 생겼어

void f()
{
    RAII r;
    bla();
}

소멸자는 어디에 RAII 처럼 보인다

RAII::~RAII()
{
    if ( std::uncaught_exception() )
    {
        // ...
    }
    else
    {
        // ...
    }
}

그러면 스택 해제 중 외부 소멸자에서 소멸자의 첫 번째 분기가 항상 사용됩니다. std::uncaught_exception() 소멸자를 포함하여 해당 소멸자에서 호출된 함수 내부에서도 항상 true를 반환합니다. RAII.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top