일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
Tags
- effective modern c++
- 티스토리챌린지
- 반복자
- 언리얼
- 게임
- 참조자
- 예외
- implicit conversion
- resource management class
- UE4
- std::async
- universal reference
- reference
- exception
- 스마트 포인터
- iterator
- 영화
- Effective c++
- Smart Pointer
- 보편 참조
- lua
- effective stl
- virtual function
- operator new
- more effective c++
- 상속
- 영화 리뷰
- 오블완
- c++
- 암시적 변환
Archives
- Today
- Total
스토리텔링 개발자
[Effective Modern C++] 14. noexcept 본문
728x90
항목 14. 예외를 방출하지 않을 함수는 noexcept로 선언하라
C++98의 예외 명세
- 함수의 구현을 수정하면 예외 명세도 바뀌는 상황이 잦았다.
- 클라이언트 코드가 깨질 수 있다.
- 호출자가 원래의 예외 명세에 의존할 수도 있기 때문이다.
- 컴파일러는 함수 구현과 예외 명세, 클라이언트 코드 사이의 일관성 유지에 아무런 도움도 안 됐다.
- 결국 C++98의 예외 명세는 득보다 실이 크게 느껴졌다.
C++11의 예외 명세
- 자세한 예외 지정보다는 예외를 하나라도 방출하는지 여부가 더 중요한 게 아닌가?
- noexcept 키워드를 사용할 수 있게 되었다.
- C++98의 예외 지정은 비권장(deprecate) 기능으로 분류되었다.
noexcept의 장점
- 호출 코드의 예외 안정성이나 효율성을 증진시킬 수 있다.
- 컴파일러가 더 나은 목적 코드(object code)를 산출할 수 있다.
int f(int x) throw(); // c++98 방식의 예외 던지지 않음 표시
// 실행 시점에서 예외가 f 바깥으로 던져지면 예외 명세 위반
// 1. 호출 스택이 f를 호출한 시점까지 rewind된다.
// 2. 실행 종료.
int f(int x) noexcept; // c++11 방식의 예외 던지지 않음 표시
// 실행 시점에서 예외가 f 바깥으로 던져지면 예외 명세 위반
// 1. 호출 스택이 f를 호출한 시점까지 rewind 될 수도 있고 아닐 수도 있다.
// 2. 실행 종료.
- 호출 스택이 rewind 안되어도 된다는 점은, 효율적인 컴파일러 코드 작성에 큰 도움이 된다.
- std::vector::push_back 같은 표준 라이브러리의 여러 함수는 '가능하면 이동하되, 필요하면 복사한다' 전략을 활용한다.
- 그 이유는 예외 안정성 때문.
- 복사 방식의 push_back
- 기존 메모리에서 새 메모리로 일일이 복사한 후
- 기존 메모리의 객체들을 파괴한다.
- 기존 메모리 객체들이 마지막에 파괴되므로 강력한 예외 안정성
- 이동 방식의 push_back
- 기본 메모리에서 새 메모리로 일일이 이동한다.
- 이동시마다 기존 메모리가 변경되므로 예외 안정성에 문제가 있을 수 있다.
- 헌데 noexcept 선언된 연산이면 이동 방식을 우선시 할 수 있을 것이다.
- swap은 여러 알고리즘에서 사용되므로 noexcept로 최적화되면 좋을 것이다.
- 아래는 표준 라이브러리에서의 배열에 대한 swap과 std::pair의 선언들이다.
template<class T, size_t N>
void swap(T (&a)[N], T (&b)[N]) noexcept(noexcept(swap(*a, *b)));
template<class T1, class T2>
struct pair
{
...
void swap(pair& p) noexcept(noexcept(swap(first, p.first)) &&
noexcept(swap(second, p.second)));
...
};
- 이 함수들은 조건부 except이다.
- 이들이 noexcept인지 여부는 noexcept절 안의 표현식들이 noexcept인지에 의존한다.
- 예컨대 Widget 배열이 두 개 있다면
- 그 둘을 교환하는 swap은 오직 배열의 개별 요소들의 swap이 noexcept일때만 noexcept이다.
- 그러므로 noexcept swap을 사용할지 말지는 swap을 작성하는 프로그래머의 몫이다.
- 즉, swap 함수를 작성할 때는 가능한 한 항상 noexcept를 지정하는 것이 좋다.
noexcept 선언 시 조심할 것
- noexcept 선언 후 나중에 마음을 바꾼다 해도 흡족한 수습 방안이 없다.
- noexcept를 제거하면, 사용한 쪽의 코드가 깨질 위험이 생긴다.
- noexcept를 유지하고 그냥 예외를 던지면, 예외가 실제 발생했을 때 크래시가 발생할 것이다.
- 대부분의 함수는 예외 중립적이다.
- 예외를 발생시키진 않지만, 내부 다른 함수에서 던져진 함수를 그냥 통과시킬 순 있다.
- 이 경우도 noexcept를 지정할 수 없다.
- 즉, noexcept가 자연스러울 때만 지정하도록 하자.
- 작위적으로 비틀어서 억지로 noexcept 지정하는 건 절대 잘될 리 없다.
암묵적으로 noexcept로 선언되는 함수
- 메모리 해제 함수(operator delete, operatr delete[])
- 모든 소멸자
- 명시적으로 예외를 던질 수 있다고 지정할 수는 있다.
넓은 계약(wide contract)과 좁은 계약(narrow contract)
- 넓은 계약
- 전제조건이 없는 함수.
- 프로그램 상태와 무관하게 호출할 수 있다.
- 호출자가 전달하는 인수들에 그 어떤 제약도 가하지 않는다.
- 결코 미정의 행동을 보이지 않는다.
- 이 경우 noexcept로 선언하는 건 쉬운 일이다.
- 좁은 계약
- 넓은 계약이 아닌 모든 함수.
- 전제조건을 위반하는 상황이 생길 수 있으므로 noexcept 선언하는 건 까다롭다.
- 위반 시 예외를 던지지 못하게 될 것이다.
함수 구현과 예외 명세 사이의 비일관성을 파악하는 데 컴파일러는 도움이 안된다.
void setup();
void cleanup();
void doWork() noexcept
{
setup();
...
cleanup();
}
- setup과 cleanup을 호출함에도 noexcept로 선언되어 있다?
- setup과 cleanup이 비록 noexcept로 선언되어 있지 않더라도 예외를 던지지 않는 함수일 수 있다.
- 그렇기 때문에 다음과 같은 코드를 컴파일러는 허용한다.(경고조차 없다.)
728x90
'Effective C++ > Effective Modern C++' 카테고리의 다른 글
[Effective Modern C++] 16. thread safety한 const 멤버 함수 (0) | 2025.02.26 |
---|---|
[Effective Modern C++] 15. constexpr (0) | 2025.02.25 |
[Effective Modern C++] 13. const_iterator 선호하기 (0) | 2025.02.21 |
[Effective Modern C++] 12. override 키워드와 참조 한정사(reference qualifier) (0) | 2025.02.20 |
[Effective Modern C++] 11. 삭제된 함수(deleted function) (0) | 2025.02.19 |
Comments