`catch (…)인가? {throw; }`나쁜 습관?


74

다시 ... 던지지 않고 잡는 것이 실제로 잘못되었다는 것에 동의 하지만, 나는 다음과 같은 구문을 사용한다고 생각합니다.

try
{
  // Stuff
}
catch (...)
{
  // Some cleanup
  throw;
}

RAII가 적용되지 않는 경우 허용됩니다 . (제발, 제발 묻지 마십시오. 우리 회사의 모든 사람이 객체 지향 프로그래밍을 좋아하지는 않으며 RAII는 종종 "무용지물"로 간주됩니다 ...)

제 동료들은 예외가 발생하는 것을 항상 알고 있어야하며 항상 다음과 같은 구문을 사용할 수 있다고 말합니다.

try
{
  // Stuff
}
catch (exception_type1&)
{
  // Some cleanup
  throw;
}
catch (exception_type2&)
{
  // Some cleanup
  throw;
}
catch (exception_type3&)
{
  // Some cleanup
  throw;
}

이러한 상황에 대해 잘 인정 된 모범 사례가 있습니까?


3
@ Pubby : 이것이 정확히 같은 질문인지 확실하지 않습니다. 링크 된 질문에 대한 자세한은 "내가 잡을해야입니다 ...있는 동안 내 질문의 초점은"나는 더 나은 캐치를해야 하는가 " ...또는 <specific exception>rethrowing 전에"
ereOn

53
유감스럽게도 RAII가없는 C ++은 C ++이 아닙니다.
fredoverflow

46
그래서 당신의 젖소 노동자들은 특정한 문제를 다루기 위해 고안된 기술을 기각 한 다음 어떤 열악한 대안을 사용해야하는지에 대해 떨리게됩니까? 유감 스럽게도 , 내가 어떤 방식으로 보더라도 그것은 어리석은 것처럼 보입니다 .
sbi

11
"다시 던지지 않고 잡는 것은 실제로 잘못된 것"-당신은 착각합니다. 에서 main, catch(...) { return EXIT_FAILURE; }잘 디버거에서 실행되지 않는 코드에서 바로 수 있습니다. 잡지 않으면 스택이 풀리지 않을 수 있습니다. 디버거가 catch하지 않은 예외를 감지 한 경우에만 예외가 발생합니다 main.
Steve Jessop

3
... "프로그래밍 오류"인 경우에도 반드시 알고 싶지 않은 것은 아닙니다. 어쨌든 동료들은 훌륭한 소프트웨어 전문가가 아니기 때문에 sbi는 만성적으로 연약한 상황을 다루는 가장 좋은 방법에 대해 이야기하는 것이 매우 어렵다고 말합니다.
Steve Jessop

답변:


196

내 동료들은 당신이 항상 어떤 예외가 던져 질지 알아야한다고 말합니다 ...]

당신의 동료, 나는 그것을 말하기 싫어, 분명히 범용 라이브러리에서 일한 적이 없었습니다.

어떻게 세계에서 같은 클래스 수 있습니다 std::vector, 심지어는 여전히 예외 안전성을 보장하면서, 복사 생성자가 발생합니다 알고?

호출자가 컴파일 타임에 수행 할 작업을 항상 알고 있다면 다형성은 쓸모가 없습니다! 때때로 전체 목표 는 낮은 수준에서 발생하는 일을 추상화하는 것이므로 구체적으로 무슨 일이 일어나고 있는지 알고 싶지 않습니다 !


32
실제로 예외가 발생 한다는 것을 알고 있었 더라도 . 이 코드 복제의 목적은 무엇입니까? 처리가 다르지 않으면 나는 당신의 지식을 과시하기 위해 예외를 열거 할 필요가 없습니다.
Michael Krelin-해커

3
@ MichaelKrelin-hacker : 그것도 마찬가지입니다. 또한 코드에 가능한 모든 예외를 나열하면 나중에 버그가 발생하는 경향이 있기 때문에 예외 사양이 더 이상 사용되지 않는다는 사실을 덧붙이십시오. 그것은 최악의 아이디어입니다.
Mehrdad

4
그리고 나를 귀찮게하는 것은 "무용 한 학교 물건"과 같은 유용하고 편리한 기술을 볼 때 그러한 태도의 기원이 될 수있는 것입니다. 그러나 잘 ...
마이클 크렐 린-해커

1
+1, 가능한 모든 옵션의 열거는 미래의 실패에 대한 훌륭한 레시피입니다. 왜 지구상에서 누군가가 그 일을 선택했을 ...까요?
littleadv

2
좋은 대답입니다. 지원 해야하는 컴파일러가 영역 X에 버그가있는 경우 영역 X의 기능을 사용하는 것이 똑똑하지 않다고 언급 할 수 있습니다. 적어도 직접 사용하지 마십시오. 예를 들어, 회사에 관한 정보를 감안할 때,이 영역에 실리 버그가있는 Visual C ++ 6.0 (예외 객체 소멸자가 두 번 호출 됨)을 사용하더라도 놀라지 않을 것입니다. 초기 버그의 작은 자손은 오늘, 그러나 표시하기 위해 신중한 조치가 필요합니다.
Alf P. Steinbach

44

당신이 붙잡힌 것처럼 보이는 것은 그들의 케이크를 먹고 그것을 먹는 누군가의 특정한 지옥입니다.

RAII 및 예외는 함께 진행되도록 설계되었습니다. RAII는하지 않는 수단이다 많이 쓰고 catch(...)정리를 할 수있는 문을. 물론 자동으로 발생합니다. 예외는 RAII 객체로 작업 할 수있는 유일한 방법입니다. 생성자는 성공하거나 던질 수만 있습니다 (또는 객체를 오류 상태로 만들지 만 누가 원합니까?).

catch문은 두 가지 중 하나를 수행 할 수 오류 또는 예외적 인 상황을 처리, 또는 정리 작업을 수행. 때로는 두 가지 모두 catch를 수행 하지만 모든 진술은 적어도 하나를 수행하기 위해 존재합니다.

catch(...)적절한 예외 처리를 수행 할 수 없습니다. 당신은 예외가 무엇인지 모른다; 예외에 대한 정보를 얻을 수 없습니다. 당신은 예외에 의해 발생했다는 사실이 아닌 전혀 정보가 없다 어떤 특정 코드 블록 내에서합니다. 그러한 블록에서 할 수있는 유일한 합법적 인 일은 정리를하는 것입니다. 그리고 이는 정리가 끝날 때 예외를 다시 발생시키는 것을 의미합니다.

예외 처리와 관련하여 RAII가 제공하는 것은 무료 정리입니다. 모든 것이 RAII로 올바르게 캡슐화되면 모든 것이 올바르게 정리됩니다. 더 이상 catch명령문을 정리할 필요가 없습니다 . 어떤 경우에도 catch(...)진술서 를 작성할 이유가 없습니다 .

그래서 나는 그 동의 할 것입니다 catch(...)... 대부분 악한 잠정적으로 .

해당 조항은 RAII를 올바르게 사용하는 것입니다. 그것을하지 않고 있기 때문에, 당신은 필요 어떤 정리를 할 수 있기를. 주변을 돌아 다니지 않습니다. 정리 작업을 수행 할 수 있어야합니다. 예외를 던질 때 코드가 적절한 상태를 유지하도록해야합니다. 그리고 그렇게 catch(...)하는 데 중요한 도구입니다.

다른 것 없이는 가질 수 없습니다. RAII catch(...) 나쁘다고 말할 수는 없습니다 . 이 중 하나 이상이 필요합니다. 그렇지 않으면 예외 안전하지 않습니다.

물론 catch(...)RAII조차도 추방 할 수없는 타당하지만 드물게 사용 exception_ptr됩니다. 다른 사람에게 전달하는 것입니다. 일반적으로 promise/future또는 유사한 인터페이스를 통해 .

제 동료들은 예외가 발생하는 것을 항상 알고 있어야하며 항상 다음과 같은 구문을 사용할 수 있다고 말합니다.

당신의 동료는 바보입니다 (또는 끔찍하게 무지합니다). 그가 작성한 복사 및 붙여 넣기 코드의 양으로 인해 이는 즉시 명백해야합니다. 각각의 catch 문에 대한 정리는 정확히 동일 합니다. 가독성은 말할 것도없고 유지 보수의 악몽입니다.

요컨대 이것은 RAII 가 해결하기 위해 만들어진 문제입니다 (다른 문제를 해결하지는 않습니다).

이 개념에 대해 저를 혼동하는 것은 대부분의 사람들이 RAII가 나쁘다는 주장에 일반적으로 반대라는 것입니다. 일반적으로 인수는 "RAII는 생성자 실패를 알리기 위해 예외를 사용해야하기 때문에 나쁘다. 그러나 안전하지 않기 때문에 예외를 던질 수 없으며 catch모든 것을 정리하기 위해 많은 문장을 가져야 한다." RAII가 부족한 RAII의 문제점을 해결 하기 때문에 이는 잘못된 주장 입니다.

아마도 그는 세부 사항을 숨기므로 RAII에 반대합니다. 소멸자 호출은 자동 변수에 즉시 표시되지 않습니다. 따라서 암시 적으로 호출되는 코드를 얻습니다. 일부 프로그래머는 정말로 그것을 싫어합니다. 분명히, 그들이 3 개의 catch문장을 가지고 있다고 생각할 때까지는, 복사-붙여 넣기 코드로 같은 일을하는 것이 더 좋습니다.


2
강력한 예외 안전 보장 을 제공하는 코드를 작성하지 않은 것 같습니다 . RAII는 기본 보증 을 제공 합니다. 그러나 강력한 보증을 제공하려면 시스템을 함수가 호출되기 전의 상태로 되 돌리는 일부 작업을 취소해야합니다. 기본 보증은 정리 , 강력한 보증은 롤백 입니다. 롤백 수행은 기능에 따라 다릅니다. 따라서 "RAII"에 넣을 수 없습니다. 그리고 그것은 catch-all block이 편리해질 때 입니다. 강력한 보장을 가진 코드를 작성하면 모두를 많이 사용합니다.
anton_rh

@anton_rh : 아마도 이러한 경우에도 catch-all 문은 최후의 수단 입니다. 기본 도구는 예외로 되돌려 야 할 상태를 변경 하기 전에 발생 하는 모든 작업을 수행하는 것입니다 . 분명히 모든 경우에 그런 식으로 모든 것을 구현할 수는 없지만 강력한 예외 보장을 얻는 이상적인 방법입니다.
Nicol Bolas

14

정말 두 가지 의견입니다. 첫 번째는 이상적인 세계에 있지만 실제로 타사 라이브러리를 처리하거나 Microsoft 컴파일러를 컴파일하는 경우 어떤 예외가 발생할 수 있는지 항상 알아야한다는 것입니다. 그러나 더 중요한 것은; 가능한 모든 예외를 정확히 알고 있더라도 여기에 관련이 있습니까? catch (...)보다 의도 훨씬 더 나은 표현 catch ( std::exception const& )도 가능한 모든 예외에서 파생 된 것으로 가정하면, std::exception(이것은 이상적인 세상에서 그렇습니다). 여러 가지 catch 블록을 사용하는 경우 모든 예외에 대한 공통 기반이 없으면 완전히 난독 화이며 유지 관리 악몽입니다. 모든 행동이 동일하다는 것을 어떻게 인식합니까? 그게 의도였습니까? 동작을 변경해야하는 경우 (예 : 버그 수정) 어떻게됩니까? 하나를 놓치기가 너무 쉽습니다.


3
사실, 내 동료는 않습니다 자신의 예외 클래스 설계 되지 에서 파생 std::exception하고 우리의 코드베이스 사이에 사용을 강제하는 일상하려고합니다. 내 생각에 그는 자신이 작성하지 않은 코드와 외부 라이브러리를 사용하여 나를 처벌하려고합니다.
ereOn

17
@ereOn 동료가 훈련이 절실히 필요한 것 같습니다. 어쨌든, 아마 그가 작성한 라이브러리를 사용하지 않을 것입니다.

2
땅콩 버터와 죽은 도마뱀처럼 템플릿에 어떤 예외가 발생하는지 알 수 있습니다. std::vector<>거의 모든 이유로 어떤 종류의 예외를 던질 수있는 것처럼 간단한 것 .
David Thornley

3
내일 버그로 인해 어떤 예외가 콜 트리 아래로 더 많이 발생하는지 정확히 어떻게 알 수 있습니까?
mattnz

11

나는 동료가 좋은 조언을 혼합했다고 생각합니다 catch. 재발견하지 않으면 알려진 예외를 한 블록으로 만 처리해야 합니다.

이것은 다음을 의미합니다.

try
{
  // Stuff
}
catch (...)
{
  // General stuff
}

이 자동으로 숨길 수 있기 때문에 나쁜 어떤 오류가 발생했습니다.

하나:

try
{
  // Stuff
}
catch (exception_type_we_can_handle&)
{
  // Deal with the known exception
}

우리는 다루고있는 것을 알고 있으며이를 호출 코드에 노출시킬 필요가 없습니다.

마찬가지로:

try
{
  // Stuff
}
catch (...)
{
  // Rollback transactions, log errors, etc
  throw;
}

일반적으로 오류를 처리하는 코드는 오류를 일으키는 코드와 관련이 있어야합니다. 트랜잭션을 롤백하거나 다른 것이 필요하다는 것을 아는 것은 수신자에게 의존하는 것보다 낫습니다.


9

또는 아니오에 대한 답은 왜 그런지에 대한 근거를 수반해야합니다.

내가 그런 식으로 배웠기 때문에 그것이 잘못되었다고 말하는 것은 단지 맹목적인 광신 론입니다.

//Some cleanup; throw코드 중복과 유지 관리 부담으로 인해 예제에서와 같이 여러 번 동일한 내용을 작성하는 것이 잘못되었습니다. 한 번만 쓰는 것이 좋습니다.

catch(...)처리하는 방법을 알고있는 예외 만 처리해야하기 때문에 모든 예외를 침묵 시키기 위해 a 를 작성하는 것은 잘못이며, 해당 와일드 카드를 사용하면 예상보다 더 많은 결과를 얻을 수 있으며, 그렇게하면 중요한 오류를 침묵시킬 수 있습니다.

그러나를 뒤에 다시 던지면 catch(...)실제로 예외를 처리하지 않기 때문에 후자의 이론적 근거가 더 이상 적용되지 않으므로 이것이 권장되지 않는 이유는 없습니다.

실제로 나는 어떤 문제없이 민감한 기능에 로그인하기 위해이 작업을 수행했습니다.

void DoSomethingImportant()
{
    try
    {
        Log("Going to do something important");
        DoIt();
    }
    catch (std::exception &e)
    {
        Log("Error doing something important: %s", e.what());
        throw;
    }
    catch (...)
    {
        Log("Unexpected error doing something important");
        throw;
    }
    Log("Success doing something important");
}

2
희망 Log(...)은 던질 수 없습니다.
중복 제거기

2

나는 일반적으로 여기 게시물의 분위기에 동의하고 특정 예외를 잡는 패턴을 정말로 좋아하지 않습니다.이 구문은 아직 초기 단계이며 중복 코드에 아직 대처할 수 없다고 생각합니다.

그러나 모두가 그렇게 말하고 있기 때문에, 나는 그것들을 조금만 사용해도 "catch (Exception e)"문장 중 하나를 자주 보았고 "아빠, 내가 전화했으면 좋겠다. "나중에 들어 왔을 때 의도가 무엇인지, 클라이언트가 한 눈에 던질 가능성이 무엇인지 아는 것이 종종 좋기 때문입니다."

나는 "항상 x를 사용한다"라는 태도를 정당화하는 것이 아니라 가끔은 실제로 그것들을 열거하는 것이 좋으며 일부 사람들은 그것이 "올바른"방법이라고 생각하는 이유라고 확신합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.