Skip to main content

C++ of the Day #42 - Use of std::bad_exception

이번 글은 Use of std::bad_exception을 보고 작성하였습니다.

아래 프로그램의 실행 결과는 무엇일까요?

void my_unexpected() {
if (!uncaught_exception())
cerr << "my_unexpected() called\n";
throw;
}

void my_terminate() {
cerr << "my_terminate() called\n";
}

void func() {
cerr << "func() called\n";
}

void g() throw (int) {
throw 1.0; // throws double
}

int main()
{
set_unexpected (my_unexpected);
set_terminate (my_terminate);
atexit (func);
try {
g();
}
catch (int) {
cerr << "caught int\n";
}
catch (bad_exception& e) {
cerr << "caught bad_exception\n";
}
return 0;
}


한 단계씩 살펴보도록 하겠습니다. 먼저 main() 함수의 처음 세 라인은 각각 unexpected, terminate 함수와 프로그램 종료시 호출될 함수를 등록하고 있습니다. 그리고 C++03에서 unexpected() 함수와 관련된 내용은 다음과 같습니다.


  1. exception-specification을 가진 함수에서 exception-specification에 리스트되어 있지 않은 예외를 던지면 호출된다.


  2. unexpected 함수안에서 exception-specification에 있는 예외를 던지면 해당 handler를 찾아 처리한다.


  3. unexpected 함수안에서 rethrow 하거나 exception-specification에 없는 예외를 던지면...

    • exception-specification에 std::bad_exception이 있으면 던져진 예외는 std::bad_exception으로 치환되어 처리된다.

    • exception-specification에 std::bad_exception이 없으면 terminate() 함수가 호출된다.






이제 main() 함수를 살펴 보죠. try 블록 안에서 g() 함수를 호출하고 있는데 g() 함수의 exception-specification에는 int 타입만이 존재합니다. 하지만 g() 함수에서는 double 타입을 던지고 있죠. 따라서 제일 먼저 unexpected 함수가 호출됩니다.

다음으로 unexpected() 함수에서는 원래 예외를 그대로 rethrow하고 있는데 g() 함수의 exception-specification에는 std::bad_exception이 없습니다. 따라서 terminate() 함수가 호출됩니다. terminate() 함수는 abort() 함수를 호출하게 되고 abort() 함수의 경우 atexit()로 등록한 함수들이 호출되지 않게 됩니다. ((C++03, 3.6.3.4. Calling the function void abort(); declared in <cstdlib> terminates the program without executing destructors for objects of automatic or static storage duration and without calling the functions passed to atexit(). ))

따라서 위 프로그램의 결과는 다음과 같습니다.


my_unexpected() called
my_terminate() called
Abort (core dumped)


그럼 위 프로그램에서 g() 함수의 exception-specification에 다음과 같이 std::bad_exception을 추가하면 결과는 어떻게 될까요?


void g() throw (std::bad_exception, int) {


unexpected에서 rethrow한 double 타입의 예외는 std::bad_exception으로 치환되어 처리됩니다. 그리고 exception handler에 std::bad_exception을 처리하는 handler가 있으므로 이 handler에서 처리되고 프로그램은 정상적으로 종료됩니다.

프로그램 종료시에 atexit()로 등록한 함수가 호출되므로 프로그램의 결과는 다음과 같습니다.


my_unexpected() called
caught bad_exception
func() called


마지막으로 위의 프로그램에서 아직 설명하지 않은 함수가 하나 있는데 바로 uncaught_exception()입니다. C++03에는 다음과 같이 설명되어 있습니다.


18.6.4 uncaught_exception [lib.uncaught]
bool uncaught_exception() throw();
1 Returns: true after completing evaluation of a throw-expression until either completing initialization of the exception-declaration in the matching handler or entering unexpected() due to the throw; or after entering terminate() for any reason other than an explicit call to terminate(). [Note: This includes stack unwinding (15.2). --end note]

2 Notes: When uncaught_exception() is true, throwing an exception can result in a call of terminate() (15.5.1).


간단히 설명하면 예외가 던져진 후부터 어떤 handler가 catch 하기 전까지만 true를 리턴하는 함수입니다. 당연하게도 handler가 다시 rethrow하게 되면 다른 handler가 받기 전까지 다시 true를 리턴합니다. ((기본적인 uncaught_exception() 함수의 동작을 위해 여기 코드를 참고하세요.))

위의 코드와 같이 던져진 예외에 의해 호출된 unexpected() 함수안에서 uncaught_exception() 함수의 리턴 값은 위에 언급된 대로 true가 되어야 합니다. 하지만 위의 결과를 보면 my_unexpected() 함수안에서 호출된 uncaught_exception() 함수가 false를 리턴했음을 알 수 있습니다. 이 점에 있어서는 g++가 표준을 따르지 않음을 알 수 있습니다. ((여기를 보니 VC++에서는 무조건 false를 리턴하도록 구현되어 있군요.))

그럼 왜 표준에선 이런 함수를 정의해 놓았을까요? 이유는 exception이 처리되면서 발생하는 stack unwinding과 관계가 있습니다. stack unwinding이 시작되면 해당 stack에 있는 객체들의 소멸자들이 호출됩니다. 그런데 exception에 의한 stack unwinding시 호출되는 소멸자에서 다시 exception이 던져되면 바로 terminate()가 호출됩니다. ((C++03, 15.2.3. [Note: If a destructor called during stack unwinding exits with an exception, terminate is called (15.5.1). So destructors should generally catch exceptions and not let them propagate out of the destructor. --end note] ))

이런 이유로 소멸자에선 예외가 빠져나가게 해서는 안된다라는 규칙도 생긴거죠. 이런 경우 uncaught_exception을 사용하여 소멸자에서 uncaught_exception의 리턴 값이 false인 경우에만 예외를 던지게 되면 terminate가 불리지 않게 할 수 있습니다. ((물론 Uncaught Exceptions에 보면 이런 사용법은 여러가지 문제가 있기 때문에 사용해서는 안됩니다.)) 이런 관점에서 봤을 때는 현재 g++에서의 구현이 C++03보다 더 맞는 것으로 보이기도 합니다. unexpected() 함수에서는 예외를 던지더라도 terminate()가 호출되진 않으니까요. :-)

마지막으로 A Pragmatic Look at Exception Specifications에 있듯이 exception specification의 사용은 권장되지 않습니다.

사용이 권장되지 않는 exception specification과 uncaught_exception() 함수에 대해 너무 길게 써버렸네요. :-)

Comments

  1. C++의 세계는 끝이 없이 깊은 것 같습니다. 그래도 C++ 무척 좋아요. 흐흐

    ReplyDelete
  2. 네, 좀 그렇죠? 어느 정도 깊이까지는 알아야겠지만 그 이후부턴 취미(?)삼아 해야 할 것 같습니다. :-) 표준에도 애매한 부분이 많아서 머리가 아프죠.

    그나저나 이번 글은 너무 비실용적이군요. :-?

    ReplyDelete
  3. VC++ 에서 무조건 false 를 리턴하지는 않는것 같습니다. http://msdn.microsoft.com/en-us/library/k1atwat8(VS.80).aspx 코드로 테스트해 보니, try 블록 내부에서 발생된 예외에 대해서는 true를 리턴하고 외부에서 발생된 것에대해서는 false 를 리턴됩니다.

    제가 틀릴수도 있으니, 한번 살펴 보시면 좋을것 같습니다. 그나저나 이 함수 잘 이해가 안되네요. 쓰라는건지 말라는건지. :)

    ReplyDelete
  4. 제가 걸었던 링크는 VC++.NET 2003 버전이었네요. 버전 올라가면서 수정되었나봐요.
    이글 쓴게 1년도 넘었으니 이해해주세요~ :)

    ReplyDelete
  5. 해당 함수는 more exceptional C++ 에선 사용하지 않은게 좋지 않겠는가? 라고 권유하고 있네요. MSVC만해도 자사 컴파일러 특성을 타는 코드이니 그럴만도 할거 같습니다.

    싸이트에 재미있는 글이 많아 자주 들러봅니다^^

    ReplyDelete
  6. 자주 들리신다니 감사... 1년도 넘어 방치 상태인데 다시 손을 대볼까 생각중입니다. :)

    ReplyDelete

Post a Comment

Popular posts from this blog

1의 개수 세기

저도 간단한 알고리즘 문제 하나... :-)

어떤 수 n이 주어졌을때 1~n까지의 수를 쭈욱 썼을때 나오는 1의 개수를 구하는 문제입니다.

예를 들어 13이라는 수가 주어지면 1~13까지의 수 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13에서 1은 1, 10, 11, 12, 13에 나오며 그 개수는 6이 됩니다. 즉, f(13)=6.

원래 문제는 f(n)=n이 되는 1이 아닌 가장 작은 수를 구하는 문제인데 이 문제의 경우에는 처음부터 쭈욱 세어나가면 되기 때문에 간단히 다음과 같이 구현을 하면 됩니다. ((한가지 주의할 점은 이전에 찾았던 n-1값을 사용하지 않고 다시 처음부터 n까지 값을 계산하면 시간이 너무 많이 걸린다는 점입니다. 위의 코드에서는 static 변수를 사용하여 이전 값에 계속 더해나가는 방법을 사용했습니다.))


#include

int count1(int n)
{
static int cnt = 1; // not 0 because n starts from 2. see main.

while (n > 0) {
if ((n % 10) == 1) ++cnt;
n /= 10;
}

return cnt;
}

int main()
{
using namespace std;

int n = 2;

while (count1(n) != n) ++n;
cout << n << endl;
}


좀 재미가 없죠? 그래서 이번 문제는 어떤 수 n에 대해서 f(n)을 O(1)시간에 구하는 알고리즘을 만드는 것입니다. 관심있으신 분들은 한번 풀어보세요. 제가 만든 코드는 내일 올려보겠습니다.

C++ of the Day #9 - Boost.Python 사용하기 #1

Python은 가장 인기있는 interpret 언어중의 하나입니다. Python의 장점 중 하나는 C/C++ 모듈과 쉽게 연동할 수 있다는 점입니다. 물론 손으로 일일히 wrapper를 만드는 것은 손이 많이 가고 에러를 만들수 있는 작업이나 SWIG등과 같은 도구를 사용하면 쉽게 python 모듈을 만들 수 있습니다.

Boost.Python은 이런 SWIG와 같이 python 모듈을 쉽게 만들 수 있도록 도와주는 라이브러리로 순수 C++만을 사용한다는 점이 SWIG와 다른 점입니다. 그리고 개인적으로는 Boost 라이브러리에 포함되어 있는 것들이 왠지 좀 더 믿음직스러워서... :-)

이번 글에서는 Boost.Python 문서에 나와 있는 예제를 가지고 간단하게 python 모듈을 만드는 방법에 대해서 알아보겠습니다.

Requirements리눅스
이 글에서는 리눅스 환경에서의 사용 방법을 설명한다.Boost.Python 라이브러리 (1.33.1)
Boost 라이브러리를 다운로드받아 아래와 유사한 명령으로 라이브러리를 빌드한다.
bjam -sTOOLS=gcc -with-python install

bjam의 --prefix 옵션으로 라이브러리가 설치될 위치를 변경할 수 있다.Python 라이브러리 (2.4.3)
Python을 다운로드 받아 빌드하여 설치한다.
위의 경우와 유사하게 configure의 --prefix 옵션으로 설치될 위치를 변경할 수 있다.

Write C++ Code다음과 같이 코드를 작성한다.

// greet.cpp #include <stdexcept> char const* greet(unsigned x) { static char const* const msgs[] = { "hello", "Boost.Python", "world!" }; if (x > 2) throw std::range_error("greet: index out of range"…

Hello Wordpress, again.

한 두주일 정도 Textpattern을 사용해봤는데 다시 Wordpress로 돌아오기로 결정했습니다. 무엇보다 스킨 변경이 너무 복잡하고 사용자층이 Wordpress에 비해 너무 앏네요. 원하는 plugin도 찾기 어렵고... :-|

그동안 Textpattern에 썼던 글들은 모두 Wordpress로 옮겼습니다. 2개 있던 댓글도 옮겼는데 그중의 하난 제가 쓴... ;-)

애초에 wp-dokuwiki plugin이 무거워서 옮겼던 것이라 이 plugin은 설치를 안할 예정인데 몇가지 아쉬운 점이 있네요.

첫째는 code highlighting 기능인데 이 기능은 예전에 만들어 놨던 것을 조금 수정해서 쓰려고 준비중입니다. 두번째는 Footnote 기능인데 찾아보니 Footnotes 0.9 Plugin for WordPress 2.0.x라는게 있네요.

이정도면 비록 wiki syntax에 비할바는 아니지만 쓸만할 것 같습니다. :-)