함수의 예외 방출 행동에 관해서 의미있는 정보는 함수가 예외를 하나라도 던지는지, 아니면 절대로 던지지 않는지에 대한 이분법적 정보뿐이다.
함수의 예외 방출 행동은 매우 중요한 사항이다. 함수의 호출자는 호출하려는 함수의 noexcept 여부를 조회할 수 있고 그 조회 결과에 따라 호출 코드의 예외 안정성이나 효율성에 영향을 미친다. const 한정자 만큼이나 중요하다.
int f(int x) throw(); // C++98 스타일
int f(int x) noexcept; // C++11 스타일
예외를 만들지 않는 함수에 noexcept를 적용하는 것은 인터페이스 설계상의 문제뿐만 아니라 실제로 컴파일러가 더 나은 목적 코드를 만들어낼 수 있다.
만약 예외를 던지지 않겠다고 명시했음에도 예외가 발생했을 때, throw는 호출 스택이 해당 함수를 호출한 지점에 도달할 때까지 풀리고 noexcept는 호출 스택이 풀릴 수도, 아닐 수도 있기 때문에 컴파일러가 조금 더 최적화를 할 수 있게 된다.
noexcept가 큰 의미를 갖는 다른 예를 한번 들어보자.
벡터에 push_back을 통해 새 요소를 집어넣을 때 용량을 초과하게 된다면 새로운 메모리를 할당받아서 크기를 확장시키고 기존에 있던 요소들을 복사가 아닌 이동시키는 것이 최적화 측면에서 더 낫다. 그런데 만약 이동 도중에 예외가 발생한다면?
이미 이동이 진행중이라 기존의 벡터가 수정된 상태이고 다시 원래대로 되돌린다고 해도 또 한번 예외가 발생할 수도 있다.
그래서 이동 연산이 예외를 방출하지 않음이 확실하지 않은 경우는 이동 대신 복사로 대체되어 수행될 수 있다.
실제로 벡터의 push_back등 표준 라이브러리의 함수들은 가능하면 이동하되 필요하면 복사를 수행한다.
이동 연산이 예외를 방출하지 않는다는 것이 확실한 경우에만 이동 연산을 수행한다.
함수가 이를 알아내는 방법이 바로 noexcept이다. 주어진 연산이 noexcept로 선언되어 있는지 여부를 점검하게 된다.
참고로 벡터의 push_back에서 이동 연산과 복사 연산을 선택할 때 사용되는 함수는 move_if_noexcept이다. 예외 방출 여부에 따라 왼값 또는 오른값을 골라서 반환시켜준다.
swap역시 noexcept가 바람직하게 적용되는 경우이다.
최적화와 관련된 좋은점에 대해서만 언급했지만 최적화보다는 정확성이 더 중요하다. noexcept는 함수의 인터페이스 일부이기 때문에 확실한 경우에만 noexcept로 선언해야 한다. 만약 나중에 제거하게 되는경우 클라이언트 코드가 깨질 가능성이 존재한다.
대부분의 함수는 예외에 중립적(다른 함수의 예외 통과)이라서 noexcept가 될 수 없다.
함수를 noexcept로 만들기 위해 구조를 작위적으로 비틀기보다는 자연스러운 noexcept 구현이 되는 것이 중요하다.
◾ noexcept는 함수의 인터페이스의 일부이다. 이는 호출자가 noexcept 여부에 의존할 수 있음을 뜻한다.
◾ noexcept 함수는 비noexcept 함수보다 최적화의 여지가 크다.
◾ noexcept는 이동 연산들과 swap, 메모리 해제 함수들, 그리고 소멸자들에 특히나 유용하다.
◾ 대부분의 함수는 noexcept가 아니라 예외에 중립적이다.
'도서 > Effective Modern C++' 카테고리의 다른 글
[3장] 항목 16 : const 멤버 함수를 스레드에 안전하게 작성하라 (0) | 2023.01.17 |
---|---|
[3장] 항목 15 : 가능하면 항상 constexpr을 사용하라 (0) | 2023.01.17 |
[3장] 항목 13 : iterator보다 const_iterator를 선호하라 (0) | 2023.01.17 |
[3장] 항목 12 : 재정의 함수들을 override로 선언하라 (0) | 2023.01.17 |
[3장] 항목 11 : 정의되지 않은 비공개 함수보다 삭제된 함수를 선호하라 (0) | 2022.12.29 |