C ++에서 예외 지정자를 사용해야합니까?


123

C ++에서는 예외 지정자를 사용하여 함수가 예외를 throw하거나 throw하지 않도록 지정할 수 있습니다. 예를 들면 :

void foo() throw(); // guaranteed not to throw an exception
void bar() throw(int); // may throw an exception of type int
void baz() throw(...); // may throw an exception of some unspecified type

다음과 같은 이유로 실제로 사용하는 것이 의문입니다.

  1. 컴파일러는 엄격한 방식으로 예외 지정자를 실제로 적용하지 않으므로 이점이 크지 않습니다. 이상적으로는 컴파일 오류가 발생합니다.
  2. 함수가 예외 지정자를 위반하면 표준 동작은 프로그램을 종료하는 것이라고 생각합니다.
  3. VS.Net에서는 throw (X)를 throw (...)로 취급하므로 표준 준수가 강하지 않습니다.

예외 지정자를 사용해야한다고 생각하십니까?
"예"또는 "아니오"로 대답하고 귀하의 대답을 정당화하는 몇 가지 이유를 제공하십시오.


7
"throw (...)"는 표준 C ++가 아닙니다. 나는 그것이 일부 컴파일러의 확장이라고 믿고 일반적으로 예외 사양이없는 것과 동일한 의미를 가지고 있습니다.
Richard Corden

답변:


97

아니.

그 이유는 다음과 같습니다.

  1. 템플릿 코드는 예외 사양으로 작성이 불가능합니다.

    template<class T>
    void f( T k )
    {
         T x( k );
         x.x();
    }

    복사본이 발생하고 매개 변수 전달이 x()발생하며 알 수없는 예외가 발생할 수 있습니다.

  2. 예외 사양은 확장 성을 방해하는 경향이 있습니다.

    virtual void open() throw( FileNotFound );

    로 진화 할 수 있습니다

    virtual void open() throw( FileNotFound, SocketNotReady, InterprocessObjectNotImplemented, HardwareUnresponsive );

    당신은 정말로 그것을 다음과 같이 쓸 수 있습니다.

    throw( ... )

    첫 번째는 확장 할 수없고 두 번째는 지나치게 야심적이며 세 번째는 가상 함수를 작성할 때 실제로 의미하는 바입니다.

  3. 레거시 코드

    다른 라이브러리에 의존하는 코드를 작성할 때 무언가 끔찍하게 잘못되었을 때 어떤 일이 발생할지 알 수 없습니다.

    int lib_f();
    
    void g() throw( k_too_small_exception )
    { 
       int k = lib_f();
       if( k < 0 ) throw k_too_small_exception();
    }

    glib_f()던질 때 종료됩니다 . 이것은 (대부분의 경우) 당신이 정말로 원하는 것이 아닙니다. std::terminate()호출해서는 안됩니다. 조용히 / 폭력적으로 죽는 것보다 스택 추적을 검색 할 수있는 처리되지 않은 예외로 애플리케이션이 충돌하도록하는 것이 항상 좋습니다.

  4. 일반적인 오류를 반환하고 예외적 인 경우에 발생하는 코드를 작성합니다.

    Error e = open( "bla.txt" );
    if( e == FileNotFound )
        MessageUser( "File bla.txt not found" );
    if( e == AccessDenied )
        MessageUser( "Failed to open bla.txt, because we don't have read rights ..." );
    if( e != Success )
        MessageUser( "Failed due to some other error, error code = " + itoa( e ) );
    
    try
    {
       std::vector<TObj> k( 1000 );
       // ...
    }
    catch( const bad_alloc& b )
    { 
       MessageUser( "out of memory, exiting process" );
       throw;
    }

그럼에도 불구하고 라이브러리에서 자체 예외를 던질 때 예외 사양을 사용하여 의도를 나타낼 수 있습니다.


1
3에서는 기술적으로 std :: unexpected가 아니라 std :: terminate가됩니다. 그러나이 함수가 호출되면 기본값은 abort ()를 호출합니다. 이것은 코어 덤프를 생성합니다. 이것이 처리되지 않은 예외보다 어떻게 더 나쁜가요? (기본적으로 같은 일을합니다.)
Greg Rogers

6
@Greg Rogers : 포착되지 않은 예외가 여전히 스택 해제를 수행합니다. 이것은 소멸자가 호출된다는 것을 의미합니다. 그리고 이러한 소멸자에서 다음과 같은 많은 작업을 수행 할 수 있습니다. 리소스가 올바르게 해제되고, 로그가 올바르게 작성되고, 다른 프로세스가 현재 프로세스가 충돌하고 있다는 알림을받습니다. 요약하면 RAII입니다.
paercebal

당신은 생략했습니다 : 이것은 try {...} catch (<specified exceptions>) { <do whatever> } catch (...) { unexpected(); ]try 블록을 원하든 원하지 않든 구조의 모든 것을 효과적으로 래핑 합니다.
David Thornley

4
@paercebal 틀 렸습니다. 포착되지 않은 예외에 대해 소멸자가 실행되는지 여부는 구현 정의입니다. 대부분의 환경은 예외가 포착되지 않으면 스택 / 실행 소멸자를 해제하지 않습니다. 당신이 이식 당신의 소멸자가 예외가 (이 모호한 가치가) 처리 던져하지 않는 경우에도 실행할 수 있도록하려는 경우, 당신은 같은 코드를 작성해야try { <<...code...>> } catch(...) /* stack guaranteed to be unwound here and dtors run */ { throw; /* pass it on to the runtime */ }
로건 Capaldo

" 스택 추적을 검색 할 수있는 처리되지 않은 예외로 응용 프로그램이 충돌하도록하는 것이 조용히 / 폭력적으로 죽는 것보다 항상 낫습니다 . " "충돌"이 어떻게 깨끗한 호출보다 낫 terminate()습니까? 그냥 전화하지 abort()그래?
curiousguy

42

C ++에서 예외 사양을 피하십시오. 귀하의 질문에 제공하는 이유는 그 이유에 대한 꽤 좋은 시작입니다.

Herb Sutter의 "A Pragmatic Look at Exception Specifications"를 참조하십시오 .


3
@awoodland : 'dynamic-exception-specifications'( throw(optional-type-id-list)) 사용은 C ++ 11에서 더 이상 사용되지 않습니다. 그들은 여전히 ​​표준에 있지만 사용을 신중하게 고려해야한다는 경고 샷이 전송 된 것 같습니다. C ++ 11은 noexcept사양과 연산자를 추가합니다 . 나는 그것에 대해 noexcept논평 할 충분한 세부 사항 을 모른다. 이 기사는 다소 자세하게 보입니다 : akrzemi1.wordpress.com/2011/06/10/using-noexcept 그리고 Dietmar Kühl은 2011 년 6 월 Overload Journal에 기사를 실었습니다
마이클 버

@MichaelBurr Only throw(something)는 쓸모없고 나쁜 생각으로 간주됩니다. throw()유용합니다.
curiousguy

" 많은 사람들이 예외 사양이 수행한다고 생각하는 것입니다. bullet 함수가 나열된 예외 만 throw (아마도 없음)하도록 보장합니다. bullet 나열된 예외 (아마도 없음) 만 throw된다는 지식을 기반으로 컴파일러 최적화를 활성화합니다. 위의 기대는 다음과 같습니다. 다시 말하지만, 옳은 것에 기만적으로 가깝습니다. "아니요, 위의 기대치는 절대적으로 정확합니다.
curiousguy

14

표준 예외 (C ++의 경우)
예외 지정자는 대부분 실패한 C ++ 표준의 실험 이라고 생각합니다 .
예외는 no throw 지정자가 유용하지만 코드가 지정자와 일치하는지 확인하기 위해 내부적으로 적절한 try catch 블록을 추가해야한다는 것입니다. Herb Sutter에는 주제에 대한 페이지가 있습니다. 고치 82

또한 예외 보장을 설명 할 가치가 있다고 생각합니다.

이것들은 기본적으로 객체의 상태가 해당 객체의 메소드를 이스케이프하는 예외에 의해 어떻게 영향을 받는지에 대한 문서입니다. 불행히도 그들은 컴파일러에 의해 강제되거나 언급되지 않습니다.
부스트 및 예외

예외 보장

보장 없음 :

예외가 메서드를 이스케이프 한 후 개체의 상태에 대한 보장이 없습니다
. 이러한 상황에서는 개체를 더 이상 사용하지 않아야합니다.

기본 보증 :

거의 모든 상황에서 이것이 방법이 제공하는 최소한의 보증이어야합니다.
이렇게하면 개체의 상태가 잘 정의되고 지속적으로 사용할 수 있습니다.

강력한 보증 : (일명 거래 보증)

이렇게하면 메서드가 성공적으로 완료
되거나 예외가 throw되고 개체 상태가 변경되지 않습니다.

던짐 없음 보장 :

이 메서드는 예외가 메서드 밖으로 전파되지 않도록합니다.
모든 소멸자는이 보장을해야합니다.
| NB 예외가 이미 전파되는 동안 예외가 소멸자를 이스케이프하는 경우
| 응용 프로그램이 종료됩니다


보증은 모든 C ++ 프로그래머가 알아야 할 사항이지만 예외 사양과 관련이있는 것 같지는 않습니다.
David Thornley

1
@David Thornley : 보증은 예외 사양이 무엇이어야하는지에 대한 것입니다 (즉, 강력한 G를 가진 메서드는 보호없이 기본 G를 가진 메서드를 호출 할 수 없습니다). 불행히도 컴파일러가 유용한 방식으로 시행 될만큼 충분히 잘 정의되어 있는지 잘 모르겠습니다.
Martin York

" 예외 사양이 수행하는 작업은 다음과 같습니다.-함수가 나열된 예외 만 throw (아마도 없음)하도록 보장합니다.-나열된 예외 (아마도 없음) 만 throw된다는 지식을 기반으로 컴파일러 최적화를 활성화합니다. 위의 기대는 다음과 같습니다. 다시 말하지만, 옳은 것에 가깝습니다. "사실, 둘 다 정확히 맞습니다.
curiousguy

@curiousguy. 이것이 컴파일 타임에 검사가 시행되기 때문에 자바가 수행하는 방식입니다. C ++는 런타임 검사입니다. 그래서 : Guarantee that functions will only throw listed exceptions (possibly none). 사실이 아니다. 함수가 이러한 예외를 throw하는 경우에만 응용 프로그램이 종료되도록 보장합니다. Enable compiler optimizations based on the knowledge that only listed exceptions (possibly none) will be thrown그렇게 할 수 없습니다. 방금 지적했기 때문에 예외가 발생하지 않을 것이라고 보장 할 수는 없습니다.
Martin York

1
@LokiAstari "컴파일 시간에 검사가 시행되기 때문에 Java가 수행하는 방식입니다 _"Java 예외 사양은 실패한 실험입니다. Java에는 가장 유용한 예외 사양 throw()이 없습니다. " 함수가 이러한 예외를 throw하는 경우에만 응용 프로그램이 종료된다는 것을 보장합니다. "아니요 "참이 아님"은 참을 의미합니다. C ++에서는 함수가를 호출하지 않는다는 보장이 없습니다 terminate(). " 방금 지적했기 때문에 예외가 발생하지 않을 것이라고 보장 할 수는 없습니다. "함수가 발생하지 않는다는 것을 보장합니다. 정확히 필요한 것입니다.
curiousguy

8

gcc는 예외 사양을 위반하면 경고를 표시합니다. 내가하는 일은 예외가 내 문서와 일치하는지 확인하기 위해 명시 적으로 "lint"모드 컴파일에서만 예외 사양을 사용하는 매크로를 사용하는 것입니다.


7

유일하게 유용한 예외 지정자는 "throw ()"입니다.


2
유용한 이유를 추가해 주시겠습니까?
buti-

2
왜 유용하지 않습니까? 일부 기능이 왼쪽 오른쪽과 가운데에 예외를 던지지 않는다는 것을 아는 것보다 더 유용한 것은 없습니다.
Matt Joiner

1
자세한 설명은 Michael Burr의 답변에 언급 된 Herb Sutter 토론을 참조하십시오.
Harold Ekstrom

4

예외 사양은 C ++에서 놀랍도록 유용한 도구가 아닙니다. 그러나 std :: unexpected와 결합하면 좋은 용도가 있습니다.

일부 프로젝트에서 내가하는 일은 예외 사양이있는 코드이고, 내 디자인의 특별한 예외를 발생시키는 함수로 set_unexpected ()를 호출하는 것입니다. 이 예외는 생성시 역 추적 (플랫폼 별 방식)을 가져오고 std :: bad_exception에서 파생됩니다 (원하는 경우 전파 할 수 있도록 허용). 보통과 같이 종료 () 호출이 발생하면 역 추적은 what ()에 의해 인쇄됩니다 (및이를 유발 한 원래 예외, 찾기 어렵지 않음). 따라서 계약이 어디에 있는지 정보를 얻습니다. 어떤 예기치 않은 라이브러리 예외가 발생했는지 등을 위반했습니다.

이렇게하면 라이브러리 예외 (표준 예외 제외)의 전파를 허용하지 않으며 std :: exception에서 모든 예외를 파생합니다. 라이브러리가 던지기로 결정하면 항상 코드를 제어 할 수 있도록 잡아서 내 계층 구조로 변환합니다. 종속 함수를 호출하는 템플릿 함수는 명백한 이유로 예외 사양을 피해야합니다. 그러나 어쨌든 라이브러리 코드가있는 템플릿 함수 인터페이스를 갖는 경우는 드뭅니다 (실제로 유용한 방식으로 템플릿을 사용하는 라이브러리는 거의 없습니다).


3

함수 선언을 둘러싼 주석보다 함수 선언을보고 싶어하는 사람들이 사용할 코드를 작성하는 경우 사양이 포착하려는 예외를 알려줍니다.

그렇지 않으면 어떤 것도 사용하는 것이 특히 유용 throw()하지 않지만 예외를 throw하지 않는다는 것을 나타냅니다.


3

아니요. 코드를 사용하고 코드 나 코드에서 호출 한 코드에서 지정하지 않은 예외가 발생하면 기본 동작은 프로그램을 즉시 종료하는 것입니다.

또한 C ++ 0x 표준의 현재 초안에서는 사용이 더 이상 사용되지 않는다고 생각합니다.


3

"throw ()"사양을 사용하면 함수가 예외를 throw하지 않는다는 것을 알고있는 경우 (또는 최소한 예외를 throw하지 않을 것이라고 약속하는 경우) 코드 흐름 분석을 수행 할 때 컴파일러가 일부 최적화를 수행 할 수 있습니다. Larry Osterman은 여기에서 이에 대해 간략하게 설명합니다.

http://blogs.msdn.com/larryosterman/archive/2006/03/22/558390.aspx


2

일반적으로 예외 지정자를 사용하지 않습니다. 그러나 문제의 함수에서 다른 예외가 발생하여 프로그램이 확실히 수정할 수없는 경우 유용 할 수 있습니다. 모든 경우에 해당 기능에서 예상 할 수있는 예외를 명확하게 문서화해야합니다.

예, 예외 지정자가있는 함수에서 throw되는 지정되지 않은 예외의 예상 동작은 terminate ()를 호출하는 것입니다.

또한 Scott Meyers가보다 효과적인 C ++에서이 주제를 다루고 있음을 주목할 것입니다. 그의 Effective C ++ 및 More Effective C ++는 적극 권장되는 책입니다.


2

예, 내부 문서에있는 경우. 또는 다른 사람들이 사용할 라이브러리를 작성하여 문서를 참조하지 않고도 무슨 일이 일어나는지 알 수 있습니다. 던지거나 던지지 않는 것은 반환 값과 거의 비슷하게 API의 일부로 간주 될 수 있습니다.

동의합니다. 컴파일러에서 Java 스타일의 정확성을 적용하는 데 실제로 유용하지는 않지만 아무것도 없거나 우연한 주석보다 낫습니다.


2

단위 테스트에 유용 할 수 있으므로 테스트를 작성할 때 실패 할 때 함수가 무엇을 던질 지 알 수 있지만 컴파일러에서이를 둘러싼 강제는 없습니다. C ++에서 필요하지 않은 추가 코드라고 생각합니다. 당신이 확신해야 할 모든 것을 선택하는 것은 코드를 읽을 수 있도록 프로젝트와 팀 구성원 전체에서 동일한 코딩 표준을 따르는 것입니다.


0

기사에서 :

http://www.boost.org/community/exception_safety.html

"예외 안전 제네릭 컨테이너를 작성하는 것은 불가능한 것으로 잘 알려져 있습니다." 이 주장은 Tom Cargill [4]의 기사에서 일반적인 스택 템플릿에 대한 예외 안전 문제를 탐구하는 기사를 참조하는 경우가 많습니다. 그의 기사에서 카길은 많은 유용한 질문을 제기했지만 불행히도 그의 문제에 대한 해결책을 제시하지 못했습니다 .1 그는 해결책이 불가능할 수 있다는 제안으로 결론을 내립니다. 불행히도 그의 기사는 많은 사람들이 그 추측에 대한 "증거"로 읽었습니다. 게시 된 이후 C ++ 표준 라이브러리 컨테이너와 같은 예외 안전 제네릭 구성 요소의 예가 많이 있습니다.

그리고 실제로 템플릿 클래스를 예외적으로 안전하게 만드는 방법을 생각할 수 있습니다. 모든 하위 클래스를 제어 할 수없는 경우 어쨌든 문제가있을 수 있습니다. 이를 위해 다양한 템플릿 클래스에서 발생하는 예외를 정의하는 클래스에 typedef를 만들 수 있습니다. 이것은 문제가 처음부터 디자인하는 것과는 달리 항상 나중에 문제를 해결하는 것이라고 생각하며, 이것이 진짜 장애물이라고 생각합니다.


-2

예외 사양 = 쓰레기, 30 세 이상의 Java 개발자에게 문의


8
30 세 이상의 자바 프로그래머는 C에 대처할 수 없다는 기분이 안 좋을 것입니다. 자바를 사용할 이유가 없습니다.
Matt Joiner
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.