Program Language/C & C++

[C++] throwing exception

야곰야곰+책벌레 2022. 1. 13. 16:06
728x90
반응형

복사하거나 이동할 수 있는 모든 유형의 예외를 throw 할 수 있다.

class No_copy {
	No_copy(const No_copy&) = delete; // prohibit copying (§17.6.4)
};
class My_error {
	// ...
};
void f(int n)
{
	switch (n) {
	case 0: throw My_error{}; // OK
	case 1: throw No_copy{}; // error : can’t copy a No_copy
	case 2: throw My_error; // error : My_error is a type, rather than an object
	}
}

catch 된 exception object는 원칙적으로 throw 된 객체의 복사본이다. (optimizer는 복사를 최소화하도록 허용되지만). 즉 throw x는 x 유형의 임시 변수를 x로 초기화합니다. 이 임시 변수는 catch 되기 전에 여러 번 더 복사될 수 있다. 예외는 적절한 핸들러를 찾을 때까지 호출된 함수에서 호출하는 함수로 전달된다. exception 유형은 일부 try 블록의 catch절에서 핸들러를 선택하는 데 사용된다. exception object의 데이터는 일반적으로 오류 메시지를 생성하거나 복구를 돕는 데 사용된다. throw 지점에서 핸들러로 exception 'up stack'을 전달하는 프로세스를 스택 해제라고 한다. 종료된 각 범위에서 소멸자가 호출되어 모든 객체가 완벽히 소멸된다.

void f()
{
	string name {"Byron"};
	try {
		string s = "in";
		g();
	}
	catch (My_error) {
		// ...
	}
}
void g()
{
	string s = "excess";
	{
		string s = "or";
		h();
	}
}
void h()
{
	string s = "not";
	throw My_error{};
	string s2 = "at all";
}

h()의 throw이후 생성된 모든 문자열은 생성의 역순으로 소멸된다. : 'not', 'or', 'excess', 'in'이지만 제어 thread에 도달한 적이 없는 'not all'는 아니며 영향을 받지 않는 'Byron'도 아니다.

exception은 포착되기 전에 잠재적으로 여러 번 복사되기 때문에 일반적으로 엄청난 양의 데이터를 사용하지는 않는다. 몇 개의 단어로 이뤄진 exception은 매우 일반적이다. exception 전달의 의미는 초기화의 의미이므로 이동 의미가 있는 유형의 객체(예:문자열)는 throw 시 비용이 많이 들지 않는다. 가장 일반적인 exception 중 일부에는 정보가 없기도 하다. 유형의 이름은 오류를 보고하기에 충분하다.

struct Some_error { };
void fct()
{
	// ...
	if (something_wrong)
		throw Some_error{};
}

직접 또는 기본 클래스로 사용할 수 있는 예외 유형의 작은 표준 라이브러리 계층이 있다.

struct My_error2 : std::runtime_error {
	const char∗ what() const noexcept { return "My_error2"; }
};

runtime_error 및 out_of_range와 같은 표준 라이브러리 예외 클래스는 문자열 인수를 생성자 인수로 사용하고 해당 문자열을 역류시키는 가상 함수 what()을 갖는다.

void g(int n) // throw some exception
{
	if (n)
		throw std::runtime_error{"I give up!"};
	else
		throw My_error2{};
}
void f(int n) // see what exception g() throws
{
	try {
		void g(n);
	}
	catch (std::exception& e) {
		cerr << e.what() << '\n';
	}
}

noexcept 함수

일부 함수는 exception을 throw하지 않고 일부는 실제로는 exception을 throw하지 않아야 한다. 이를 나타내기 위해 이러한 함수 noexcept를 선언할 수 있다.

double compute(double) noexcept; // may not throw an exception

이렇게 하면 compute()에서 예외가 발생하지 않는다.

noexcept함수를 선언하는 것은 프로그래머가 프로그램에 대해 추론하고 프로그램을 최적화하는 컴파일러에게 가장 가치 있을 수 있다. 프로그래머는 (noexcept 함수에서 실패를 처리하기 위해) try-clauses를 제공하는 것에 걱정할 필요가 없으며 최적화 프로그램은 예외 처리에서 제어 경로에 대해 걱정할 필요가 없다.

그러나 noexcept는 컴파일러와 링크에서 완전히 확인되지 않는다. 프로그래머가 '거짓말'을 해서 noexcept함수가 noexcept함수를 떠나기 전에 catch 되지 않은 예외를 의도적으로 혹은 실수로 던졌다면 어떻게 될까?

double compute(double x) noexcept;
{
	string s = "Courtney and Anya";
	vector<double> tmp(10);
	// ...
}

vector 생성자는 10개의 double에 대한 메모리 획득에 실패하고 std::bad_alloc을 throw할 수 있다. 이 경우 프로그램은 종료된다. std::terminate()를 호출하여 무조건 종료된다. 함수 호출에서 소멸자를 호출하지 않는다. throw와 noexcept 사이의 범위에서 소멸자가 호출되는지 여부는 구현에서 정의된다. 프로그램이 막 종료되려고 하므로 어떤 객체에도 의존해서는 안된다. noexcept 지정자를 추가하여 코드가 throw에 대처하도록 작성되지 않았음을 나타낸다.

noexcept operator

조건부 noexcept 함수를 선언할 수 있다.

template<typename T>
void my_fct(T& x) noexcept(Is_pod<T>());

noexcept(Is_pod<T>())는 조건자 Is_pod<T>()가 true면 My_fct가 throw 되지 않고 false면 throw 할 수 있음을 의미한다. my_fct()가 인수를 복사하는 경우 이것을 작성하고 싶을 수 있다. POD를 복사하면 throw 되지 않는 반명 다른 유형(예:string or vector)은 throw 될 수 있다.

noexcept() 사양의 술어는 상수 표현식이어야 한다. 일반 noexcept는 noexcept(true)를 의미한다.

표준 라이브러리는 함수가 exception을 던질 수 있는 조건을 표현하는데 유용할 수 있는 많은 유형 술어를 제공한다.

사용하고자 하는 술어가 type 술어만으로 쉽게 표현되지 않는다면? 예를 들어, throw 할 수 있고 하지 않을 수도 있는 중요한 작업이 f(x) 함수 호출이면 어떻게 될까? noexcept() 연산자는 표현식을 인수로 사용하고 컴파일러가 throw 할 수 없다는 것을 알고 있으면 true를 반환하고 그렇지 않으면 false를 반환한다.

template<typename T>
void call_f(vector<T>& v) noexcept(noexcept(f(v[0]))
{
	for (auto x : v)
	f(x);
}

noexcept의 이중 언급은 약간 이상해 보이지만 noexcept는 일반적인 연산자가 아니다.

noexcept()의 피연산자는 평가되지 않으므로 예제에서 빈 vector와 함께 call_f()를 전달하면 런타임 오류가 발생하지 않는다.

noexcept(expr) 연산자는 expr이 던질 수 있는지 여부를 결정하기 위해 엄청난 길이로 이동하지 않는다. 단순히 expr의 모든 작업을 살펴보고 true로 평가되는 noexcept 사양이 있으면 true로 반환한다. noexcept(expr)는 expr에 사용된 작업의 정의 내부를 살펴보지 않는다.

조건부 noexcept 사양과 noexcept() 연산자는 컨테이너에 적용되는 표준 라이브러리 작업에서 일반적이고 중요하다.

template<class T, siz e_t N>
void swap(T (&a)[N], T (&b)[N]) noexcept(noexcept(swap(∗a, ∗b)));

예외 사양

이전 C++ 코드에서 예외 사양을 찾을 수 있다.

void f(int) throw(Bad,Worse); // may only throw Bad or Worse exceptions
void g(int) throw(); // may not throw

빈 예외 사양 throw()는 noexcept와 동일하게 정의된다. 즉, 예외가 발생하면 프로그램은 종료된다.

throw(Bad, Worse)와 같은 비어 있지 않은 예외 사양의 의미는 함수가 목록에 언급되지 않았거나 언급된 예외에서 공개적으로 파생된 예외를 throw 하는 경우 예기치 않은 핸들러가 발생한다는 것이다. 예기치 않은 예외의 기본 효과는 프로그램을 종료하는 것이다. 비어 있지 않은 throw 사양은 잘 사용하기 어렵고 올바른 예외가 throw 되는지 확인하기 위해 잠재적으로 비용이 많이 드는 런타임 검사를 의미한다. 이 기능은 성공하지 못했고 더 이상 사용되지 않는다.

728x90
반응형

'Program Language > C & C++' 카테고리의 다른 글

[C++] namespace를 이용한 version 관리  (0) 2022.01.14
[C++] Catching Exception  (0) 2022.01.13
[C++] Finally  (0) 2022.01.13
[C++] 리소스 관리  (0) 2022.01.13
[C++] 미리 정의된 매크로  (0) 2022.01.13