Namespaces
Variants

Throwing exceptions

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
throw -expression
try block
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

예외를 throw 하면 제어가 핸들러 로 전달됩니다.

예외는 throw expressions 에서 발생할 수 있으며, 다음 상황들도 예외를 발생시킬 수 있습니다:

목차

예외 객체

예외를 던지면 동적 storage duration 을 가진 객체가 초기화되며, 이를 exception object 라고 합니다.

예외 객체의 타입이 다음 타입 중 하나인 경우, 프로그램의 형식이 올바르지 않습니다:

예외 객체 생성 및 소멸

예외 객체의 타입이 T 로 주어졌을 때:

  • obj const T 타입의 lvalue라고 할 때, obj 로부터 T 타입 객체의 복사 초기화 가 올바르게 형성되어야 합니다.
  • 만약 T 가 클래스 타입인 경우:

예외 객체를 위한 메모리는 지정되지 않은 방식으로 할당됩니다. 유일하게 보장되는 것은 해당 저장 공간이 전역 allocation functions 에 의해 절대 할당되지 않는다는 점입니다.

만약 핸들러 재던지기 를 통해 종료되면, 제어는 동일한 예외 객체에 대한 다른 핸들러로 전달됩니다. 이 경우 예외 객체는 소멸되지 않습니다.

예외에 대한 마지막 남은 활성 핸들러가 재던지기(rethrowing) 이외의 방법으로 종료될 때, 예외 객체는 파괴되며 구현은 임시 객체에 대한 메모리를 비명시적인 방식으로 할당 해제할 수 있습니다.

파괴는 핸들러의 "매개변수 목록"에 선언된 객체의 파괴 직후에 발생합니다.

(until C++11)

예외 객체에 대한 잠재적 파괴 시점은 다음과 같습니다:

  • 예외에 대한 활성 핸들러가 재던지기 이외의 방법으로 종료될 때, 핸들러의 "매개변수 목록"에 선언된 객체(있는 경우)의 파괴 직후
  • 예외 객체를 참조하는 std::exception_ptr 타입의 객체가 파괴될 때, std::exception_ptr 의 소멸자가 반환되기 전

예외 객체에 대한 모든 잠재적 파괴 시점 중, 예외 객체가 파괴되는 비명시적인 마지막 시점이 존재합니다. 다른 모든 시점들은 이 마지막 시점보다 먼저 발생합니다 . 구현은 그 후 예외 객체에 대한 메모리를 비명시적인 방식으로 할당 해제할 수 있습니다.

(since C++11)

throw 표현식

throw expression (1)
throw (2)
1) 새로운 예외를 발생시킵니다.
2) 현재 처리 중인 예외를 다시 던집니다.
expression - 예외 객체를 생성하는 데 사용되는 표현식


새로운 예외가 throw될 때, 해당 예외 객체는 다음과 같이 결정됩니다:

  1. 배열-포인터 변환 함수-포인터 변환 표준 변환이 expression 에 대해 수행됩니다.
  2. 변환 결과를 ex 라고 하겠습니다:
  • 예외 객체의 타입은 ex 의 타입에서 최상위 cv-qualifier를 모두 제거하여 결정됩니다.
  • 예외 객체는 copy-initialized ex 로부터 초기화됩니다.

프로그램이 현재 처리 중인 예외가 없을 때 예외를 다시 던지려고 시도하면, std::terminate 가 호출됩니다. 그렇지 않으면, 기존 예외 객체로 예외가 재활성화되고(새 예외 객체가 생성되지 않음), 해당 예외는 더 이상 catch된 것으로 간주되지 않습니다.

try
{
    // 새로운 예외 123을 throw 함
    throw 123;
}
catch (...) // 모든 예외를 catch 함
{
    // 예외 123에 대해 (부분적으로) 응답
    throw; // 예외를 다른 핸들러로 전달
}

스택 풀기

예외 객체가 생성되면, 제어 흐름은 역방향(호출 스택을 거슬러 올라가며)으로 진행되어 try block 의 시작 지점에 도달할 때까지 계속됩니다. 이 지점에서 모든 관련 핸들러의 매개변수들은 예외 객체의 타입과 비교되어 match 를 찾기 위해 나타난 순서대로 검사됩니다. 일치하는 항목이 발견되지 않으면, 제어 흐름은 스택을 풀어 다음 try block으로 이동하며 이 과정을 반복합니다. 일치하는 항목이 발견되면, 제어 흐름은 해당 핸들러로 이동합니다.

제어 흐름이 호출 스택을 따라 상승함에 따라, 해당 try 블록이 시작된 이후 생성되었지만 아직 소멸되지 않은 자동 저장 기간 을 가진 모든 객체들에 대해 소멸자들이 호출됩니다. 이때 소멸자 호출은 생성자 완료 순서의 역순으로 진행됩니다. 지역 변수의 소멸자나 return 문에서 사용된 임시 객체의 소멸자에서 예외가 발생하면, 함수에서 반환되는 객체의 소멸자도 함께 호출됩니다.

객체의 생성자에서 또는 (드물게) 소멸자에서 예외가 발생하는 경우(객체의 저장 기간과 관계없이), 완전히 생성된 비정적 비배리언트 멤버 및 기본 클래스들의 소멸자들이 해당 생성자들의 완료 순서의 역순으로 호출됩니다. union-like 클래스들 의 배리언트 멤버들은 생성자로부터의 풀림(unwinding) 상황에서만 소멸되며, 초기화와 소멸 사이에 활성 멤버가 변경된 경우 그 동작은 정의되지 않습니다.

위임 생성자가 비위임 생성자가 성공적으로 완료된 후 예외와 함께 종료되면, 이 객체의 소멸자가 호출됩니다.

(since C++11)

예외가 new-expression 에 의해 호출되는 생성자에서 발생하면, 사용 가능한 경우 해당 deallocation function 이 호출됩니다.

이 과정을 stack unwinding 이라고 합니다.

예외 객체의 초기화 이후, 예외 처리기의 시작 이전에 스택 풀기 메커니즘에 의해 직접 호출되는 함수 중 하나가 예외와 함께 종료되면, std::terminate 가 호출됩니다. 이러한 함수에는 범위를 벗어나는 자동 저장 기간 객체의 destructors 와, 값에 의한 catch 인수를 초기화하기 위해 호출되는 (만약 elided 되지 않았다면) 예외 객체의 복사 생성자가 포함됩니다.

예외가 발생하여 catch되지 않는 경우, std::thread 의 초기 함수, main 함수, 그리고 정적 또는 스레드-로컬 객체의 생성자나 소멸자에서 발생하는 예외를 포함하여, std::terminate 가 호출됩니다. catch되지 않는 예외에 대해 스택 풀기(stack unwinding)가 발생하는지는 구현에 따라 정의됩니다.

참고 사항

예외를 다시 던질 때, (일반적인 경우인) 예외 객체가 상속을 사용하는 경우 객체 슬라이싱을 피하기 위해 두 번째 형식을 사용해야 합니다:

try
{
    std::string("abc").substr(10); // std::out_of_range 예외를 발생시킴
}
catch (const std::exception& e)
{
    std::cout << e.what() << '\n';
//  throw e; // std::exception 타입의 새로운 예외 객체를 복사 초기화
    throw;   // std::out_of_range 타입의 예외 객체를 재발생
}

throw -표현식은 prvalue expression 로 분류되며, 타입은 void 입니다. 다른 모든 표현식과 마찬가지로, 이는 다른 표현식의 하위 표현식이 될 수 있으며, 가장 일반적으로 conditional operator 에서 사용됩니다:

double f(double d)
{
    return d > 1e7 ? throw std::overflow_error("too big") : d;
}
int main()
{
    try
    {
        std::cout << f(1e10) << '\n';
    }
    catch (const std::overflow_error& e)
    {
        std::cout << e.what() << '\n';
    }
}

키워드

throw

예제

#include <iostream>
#include <stdexcept>
struct A
{
    int n;
    A(int n = 0): n(n) { std::cout << "A(" << n << ") constructed successfully\n"; }
    ~A() { std::cout << "A(" << n << ") destroyed\n"; }
};
int foo()
{
    throw std::runtime_error("error");
}
struct B
{
    A a1, a2, a3;
    B() try : a1(1), a2(foo()), a3(3)
    {
        std::cout << "B constructed successfully\n";
    }
    catch(...)
    {
        std::cout << "B::B() exiting with exception\n";
    }
    ~B() { std::cout << "B destroyed\n"; }
};
struct C : A, B
{
    C() try
    {
        std::cout << "C::C() completed successfully\n";
    }
    catch(...)
    {
        std::cout << "C::C() exiting with exception\n";
    }
    ~C() { std::cout << "C destroyed\n"; }
};
int main () try
{
    // A 기본 하위 객체 생성
    // B의 a1 멤버 생성
    // B의 a2 멤버 생성 실패
    // 언와인딩으로 B의 a1 멤버 파괴
    // 언와인딩으로 A 기본 하위 객체 파괴
    C c;
}
catch (const std::exception& e)
{
    std::cout << "main() failed to create C with: " << e.what();
}

출력:

A(0) constructed successfully
A(1) constructed successfully
A(1) destroyed
B::B() exiting with exception
A(0) destroyed
C::C() exiting with exception
main() failed to create C with: error

결함 보고서

다음의 동작 변경 결함 보고서들은 이전에 발표된 C++ 표준에 소급 적용되었습니다.

DR 적용 대상 게시된 동작 올바른 동작
CWG 499 C++98 경계가 알려지지 않은 배열은 해당 타입이 불완전하기 때문에
throw될 수 없었으나, decay된 포인터로부터 예외 객체는
아무 문제없이 생성될 수 있었음
타입 완성 요구사항을
예외 객체에
적용하도록 변경
CWG 668 C++98 std::terminate 가 지역 비자동 객체의 소멸자에서
예외가 발생했을 때 호출되지 않음
이 경우 std::terminate
를 호출하도록 변경
CWG 1863 C++11 move-only 예외 객체를 throw할 때 복사 생성자가
필요하지 않았으나, 이후 복사가 허용됨
복사 생성자 필요
CWG 1866 C++98 생성자에서 스택 풀기 시 variant 멤버가 누출됨 variant 멤버 소멸
CWG 2176 C++98 지역 변수 소멸자에서의 throw가
반환 값 소멸자를 건너뛸 수 있었음
함수 반환 값을
풀기 과정에 추가
CWG 2699 C++98 throw "EX" 가 실제로 char * 를 throw했음 ( const char * 가 아님) 수정됨
CWG 2711 C++98 예외 객체의 복사 초기화 소스가
명시되지 않았음
expression 으로부터
복사 초기화됨
CWG 2775 C++98 예외 객체 복사 초기화 요구사항이 불명확했음 명확하게 지정
CWG 2854 C++98 예외 객체의 저장 기간이 불명확했음 명확하게 지정
P1825R0 C++11 throw 에서 매개변수로부터의 암시적 move가 금지됨 허용됨

참조문헌

  • C++23 표준(ISO/IEC 14882:2024):
  • 7.6.18 예외 던지기 [expr.throw]
  • 14.2 예외 던지기 [except.throw]
  • C++20 표준 (ISO/IEC 14882:2020):
  • 7.6.18 예외 던지기 [expr.throw]
  • 14.2 예외 던지기 [except.throw]
  • C++17 표준(ISO/IEC 14882:2017):
  • 8.17 예외 던지기 [expr.throw]
  • 18.1 예외 던지기 [except.throw]
  • C++14 표준(ISO/IEC 14882:2014):
  • 15.1 예외 던지기 [except.throw]
  • C++11 표준(ISO/IEC 14882:2011):
  • 15.1 예외 던지기 [except.throw]
  • C++03 표준(ISO/IEC 14882:2003):
  • 15.1 예외 던지기 [except.throw]
  • C++98 표준(ISO/IEC 14882:1998):
  • 15.1 예외 던지기 [except.throw]

참고 항목

(C++17까지)