C++/C++

[exception specifier] noexcept

Elan 2021. 7. 11. 17:32

C++의 예외 처리 과정

 

1. 기본적으로 함수는 예외(exception)가 발생할 수 있다.

 예외가 발생할 경우 예외를 담당하는 객체가 생성된다.

 

2. 함수에서 예외가 발생하면 가장 가까운 try~catch 블록을 찾아서

 발생한 예외를 처리할 수 있는지 검사한다.

  만약 해당 예외를 처리할 수 있는 catch 블록을 발견할 경우

 예외 객체를 전달하고 예외 처리를 진행한다.

 이때 예외가 발생(throw)한 지점부터 catch블록 사이에 생겨난 변수들은 모두 소멸한다.

 예외 발생 지점과 catch 구문 사이의 스택 변수를 소멸시키는 과정을 stack unwinding이라 하는데,

 이 동작을 하는 코드는 컴파일러가 알아서 생성한다.



3. 예외가 발생하면 예외가 발생한 블럭을 포함하는 가장 가까운 try구문을 찾을 때까지
   상위 스코프로 거슬러 올라간다.

  try구문을 찾았다면 연결된 catch 구문들 중에서 예외를 처리할 수 있는 영역에서 예외를 처리한다.

 만약 연결된 catch 구문에서 예외를 처리할 수 없을 경우 위의 과정을 반복한다.

 (만약 끝까지 예외 처리를 할 수 있는 영역을 만나지 못한다면 std::terminate를 호출하고 프로그램이 종료된다)


 

 

Non - throwing

 

만약 특정 함수가 예외를 발생시키지 않는다는 것을 보장할 수 있다면?


컴파일러에게 예외처리를 위한 코드를 생성할 필요가 없다고 알려줄 수 있다.

이것은 컴파일러에게 최적화 힌트를 주므로써 성능 상 이득을 얻을 가능성이 생긴다.

noexcept 키워드가 그 역할을 한다.

즉, noexcept는 컴파일러에게 해당 함수에 대한 예외 처리 코드를 생성시키지 못하도록 하는 지시어이다.

 

참고 - https://docs.microsoft.com/en-us/cpp/cpp/exception-specifications-throw-cpp?view=msvc-160

 

 

함수 뒤에 noexcept, noexcept(true) 또는 throw()를 적어주면 해당 함수에서 예외가 발생할 경우

프로그램은 std::terminate를 호출하고 종료된다.

그러므로 noexcept키워드는

해당 함수가 절대로 예외가 발생하지 않는다는 확신이 있을 경우 또는

예외를 전파시키지 않고 즉시 프로그램에 크래쉬가 발생하도록 하기 위해서만 사용해야 한다.

 

Non-throwing 구현 방법

 

noexcept

noexcept 키워드를 추가할 경우 함수는 예외 발생를 허용하지 않는다.
따라서 아래 함수가 예외를 발생 시킬 경우 프로그램은 즉시 중단된다.

void MyFunction(int i) noexcept;

 

noexcept( expression )

noexcept( expression ) 구문을 사용할 경우 expression이 true로 평가될 경우 noexcept와 같은 동작을 하며,

expression이 false로 평가될 경우 예외를 발생시킨다.

void MyFunction(int i) noexcept(true);

 

아래는 간단한 예외 발생과 예외 처리를 하는 코드 예시이며, 실행 시 정상적으로 예외를 처리한다.

 

class MyException {};

void ThrowMyException() {
	throw MyException();
}

int main() {

	try {
		ThrowMyException();
	}
	catch (MyException e) {
		std::cout << "MyException" << "\n";
	}
    return 0;
}|


만약 위의 예제 처리 코드에서 ThrowMyException 함수가 noexcept라면?

try~catch 구문에 의해 예외 처리가 진행되기도 전에 CRASH가 발생한다.

< noexcept가 적용 된 함수에서 예외 발생 시 >

 

 

throw( )

C++11 부터는 사용하지 않을 것을 권장한다. ( deprecated with C++11 and will be removed with C++20 )

예외를 생성하는 throw(...)에 인자로 아무것도 전달하지 않을 경우 noexcept와 같은 동작을 한다.

실제로 MSVC++에서 테스트 해본결과 아무 동작을 하지 않는다.

그래서 사용하지 않기로 했다

void MyFunction(int i) throw();

참고 -  https://docs.microsoft.com/en-us/cpp/cpp/exception-specifications-throw-cpp?view=msvc-160

 

type traits library를 이용

#include <type_traits>

template <typename T>
T copy_object(const T& obj) noexcept(std::is_pod<T>)
{
   // ...
}


위 코드에서 T가 Plain Old Type일 경우 noexcept와 같은 동작을 한다.

template <typename T>
T copy(T const& src) noexcept(std::is_nothrow_copy_constructible<T>::value)
{
	return src;
}

 

 

type_trait 참고 - https://en.cppreference.com/w/cpp/header/type_traits

 

Standard library header - cppreference.com

This header is part of the type support library. Property queries obtains the type's alignment requirements (class template) [edit] obtains the number of dimensions of an array type (class template) [edit] obtains the size of an array type along a specifie

en.cppreference.com

 

 

noexcept를 붙일 수 있는 곳은 최대한 붙여주는 것이 좋은 습관이다.

STL 컨테이너를 사용할 때 경우에 따라서는
이동 생성자/이동 대입 연산자를 정의했더라도
noexcept가 붙은 이동 생성자/이동 대입 연산자가 아니라면
이동이 아닌 복사로 넘겨버리게 된다.

최적화가 안될수도 있다는 말이다.