E) noexcept

C++11 부터 throw()가 derpecated 되고, noexcept 키워드가 추가되었다.
noexcept는 연산자(operator)와 한정자(specifier)의 형태로 제공된다.
operator
noexcept(expression);
noexcept는 컴파일 타임에 해당 표현식이 예외를 던지지 않는 표현식인지 체크하여,
true/false를 반환한다.

표현식이 아래의 경우 중 하나라도 포함하면, false를 반환한다. 그렇지 않으면 true를 반환
- 상수 표현식이 아닌 함수가 noexcept 키워드를 가지지 않는 경우
- 런타임 체크가 필요한 dynamic_cast 등의 RTTI가 포함된 경우
- typeid 표현식에 포함된 타입이 상속 관계에 있는 클래스나 구조체일 경우
#include <iostream>
#include <utility>
#include <vector>
void may_throw();
void no_thorw() noexcept;
auto lmay_throw=[](){};
auto lno_throw=[]()noexcept{};
class T{
public:
// 명시적 소멸자 선언으로 이동생성자/이동연산자 암시적 생성 금지
// 복사생성자/대입연산자는 noexcept로 암시적 생성
~T(){}
};
class U{
std::vector<int> v;
public:
// 명시적 소멸자 선언으로 이동생성자/이동연산자 암시적 생성 금지
// 복사생성자/대입연산자는 noexcept(false)로 암시적 생성
// vector<int> v의 복사생성자/대입연산자는 noexcept가 아님
~U(){}
};
class V{
public:
// 이동생성자/이동연산자는 noexcept로 암시적 생성
// 복사생성자/대입연산자는 noexcept(false)로 암시적 생성
// vector<int> v의 복사생성자/대입연산자가 noexcept가 아님
std::vector<int> v;
};
int main(void){
T t;
U u;
V v;
noexcept(may_throw()); // F
noexcept(no_throw()); // T
noexcept(lmay_throw()); // F
noexcept(lno_throw()); // T
noexcept(std::declval<T>().~T()); // T 명시적 정의 소멸자는 noexcept
noexcept(std::declval<T>()); // T T(rvalue t)는 이동생성자가 없으므로 복사생성자가 noexcept
noexcept(T(t)); // T T(lvalue t) 복사 생성자가 noexcept
noexcept(U(std::declval<U>())); // F U(rvalue u)는 이동생성자가 없으므로 복사생성자가 noexcept(false)
noexcept(U(u)); // F U(lvalue u)는 복사생성자가 noexcept(false)
noexcept(V(std::declval<V>())); // T V(rvalue v)는 이동생성자가 noexcept
noexcept(V(v)); // F V(lvalue v)는 복사생성자가 noexcept(false)
}
specifier
// 1. 표현식은 상수 표현식이거나, bool로 변환가능해야 한다.
noexcept(expression)
// 2. noexcept(true)와 동일
noexcept
noexcept 한정자는 함수가 예외를 던질 수 있는지(false) 없는지(true)를 명시한다.
template <typename T>
void foo() noexcept(true){}
void baz() noexcept{ throw 42; }
int main(void){
foo<int>();
bar(); // OK
baz(); // 컴파일 문제는 없으나, 런타임에 std::terminate가 호출된다.
}
std::terminate가 호출되면, std::terminated_handler를 호출하는데
기본 handler 함수는 std::abort로 호출되면 프로그램이 종료된다.
Strong exception guarantee
#include <iostream>
#include <vector>
struct foo{
int value;
explicit foo(int value): value(value){
std::cout<<"foo("<<value<<"\n";
}
foo(const foo& other): value(other.value){
std::cout<<"foo(foo("<<value<<"))\n";
}
foo(foo&& other) noexcept: value(std::move(other.value)){
other.value=-1;
std::cout<<"foo(move(foo("<<value<<"))\n";
}
~foo(){
if(value!=-1) std::cout<<"~foo("<<value<<"\n";
}
};
int main(void){
std::vector<foo> foos;
foos.emplace_back(1);
// 벡터를 확장하기 위해 reserve가 발생한다.
// foo의 이동생성자가 noexcept를 보장하지 않으면, copyconstruction이 발생
// reallocating 과정에서 이동이 아닌 복사가 발생한다.
foos.emplace_back(2);
}
C++11 이후 std::vector를 포함한 STL 컨테이너들은 move semantics가 모두 적용되어 있다.
원소에 대한 이동처리를 할 때, 해당 원소가 move시 noexcept를 지원하지 않으면, move semantics가 아닌
copy semantics로 elements를 처리한다.