Exceptions
예외
- 예측하지 못한, 프로그램을 정상적으로 실행할 수 없는 상황에 대한 대처방법을 기술하는 기능이다.
- 구형 컴파일러에서는 지원되지 않을 수 있다.
- 일부 컴파일러에서는 Exceptions 기능을 Disable해 놓는 것이 Default Options이다.
\(\texttt{std::abort()}\) 함수
- \(\texttt{cstdlib}\) 또는 \(\texttt{stdlib.h}\) 헤더파일에 정의되어 있는 함수이다.
- \(\texttt{cerr}\)가 사용하는 스트림(표준 에러 스트림)에 "abnormal program termination" 메시지를
출력하고, 프로그램을 종료시킨다.
(\(\texttt{abort()}\)를 호출한 함수를 종료하는 것이 아닌, 프로그램 자체를 종료시킨다.)
- \(\texttt{abort()}\) 함수를 호출한 프로그램이 다른 프로그램에 의해 구동되는 Child Process(자식 프로세스)인 경우,
Parent Process(부모 프로세스) 혹은 OS(운영체제)에게 컴파일러에 종속적인 특정값을 리턴하게 된다.
- \(\texttt{abort()}\) 함수가 File Buffer*를 비우는지에 대한 여부는 시스템마다 상이하다.
- \(\texttt{exit()}\) 함수를 명시적으로 사용하여 메시지를 출력하지 않고, File Buffer만 비울 수 있다.
* File Buffer
- 파일에 전송할 내용, 파일로부터 전송받을 내용을 저장하는 데 사용되는 메모리 영역을 의미한다.
// Harmonic Mean (조화 평균) 계산 프로그램
// abort() 함수를 이용하여 예외상황을 비정상 종료시키는 코드
#include <iostream>
#include <cstdlib>
double hmean(double a, double b);
int main() {
double x, y, z;
std::cout << "Enter two numbers: ";
while (std::cin >> x >> y) {
z = hmean(x,y);
std::cout << "Harmonic mean of " << x << " and " << y << " is " << z << std::endl;
std::cout << "Enter next set of numbers <q to quit>: ";
}
std::cout << "Bye!\n";
return 0;
}
double hmean(double a, double b) {
if (a == -b) {
std::cout << "untenable arguments to hmean()\n";
std::abort();
}
return 2.0 * a * b / (a + b);
}
Returning an Error Code (에러 코드 리턴)
- 함수의 리턴값을 이용하여 문제의 종류를 구분지을 수 있게하는 방법이다.
- 예외를 무조건 비정상 종료시키는 방법보다, 예외 상황에 더 유연하게 대처하는 형태이다.
ex) ostream 클래스의 get(void) 메서드는 파일 내용의 끝(예외 상황)을 감지하면 EOF를 리턴한다.
// Harmonic Mean (조화 평균) 계산 프로그램
// 에러 코드를 리턴함으로써 예외상황에 대처하는 코드
#include <iostream>
#include <cfloat> // (or float.h) for DBL_MAX
bool hmean(double a, double b, double * ans);
int main() {
double x, y, z;
std::cout << "Enter two numbers: ";
while (std::cin >> x >> y) {
if (hmean(x,y,&z))
std::cout << "Harmonic mean of " << x << " and " << y << " is " << z << std::endl;
else
std::cout << "One value should not be the negative " << "of the other - try again.\n";
std::cout << "Enter next set of numbers <q to quit>: ";
}
std::cout << "Bye!\n";
return 0;
}
bool hmean(double a, double b, double * ans) {
if (a == -b) {
*ans = DBL_MAX;
return false;
}
else {
*ans = 2.0 * a * b / (a + b);
return true;
}
}
※ 리턴 조건을 위 프로그램처럼 포인터/참조형 매개변수로 넘기는 대신,
전역변수로 선언하여 호출하는 함수가 항시 확인할 수 있도록 하는 기법도 있다.
(전통적인 C math Library에서 사용되는 방법이다.)
C++ Exception Mechanism (C++ 예외 메커니즘)
- C++에서 제공하는 예외 처리 메커니즘은 예외가 발생한 부분에서
미리 지정해 놓은 지점(Exception Handler)으로 제어를 넘기는 방식으로 진행된다.
- 예외가 발생하면, 발생한 부분의 함수를 종료하고, try Block이 기술되어 있는 함수를 발견할 때 까지
함수들을 거슬러 올라간다.
※ C++ 예외 처리 메커니즘의 절차
1. Throwing an exception (예외 발생)
2. Catching an exception with a handler (핸들러를 이용한 예외 포착)
3. Using a \(\texttt{try}\) block (\(\texttt{try}\) 블록 사용)
Keyword: \(\texttt{throw}\)
- 예외 발생 시, 출력할 내용을 기술한다.
- \(\texttt{throw}\) 키워드의 뒤에는 예외 상황을 기술한 문자열 혹은 객체와 같은 하나의 값을 기술한다.
- \(\texttt{throw}\)문은 기본적으로 객체의 복사본을 \(\texttt{catch}\) Block에 전달한다.
(\(\texttt{catch}\) Block과 \(\texttt{throw}\)가 넘기는 객체의 Scope가 겹치지 않는 경우가 더 많기 때문이다.)
throw "Unexpected error.";
※ 또한, \(\texttt{throw}\) 뒤에 아무런 객체도 위치시키지 않음으로써,
그 위치(Callee)에서 Caller로 제어를 넘기는데 이용하기도 한다.
- 기본적으로, \(\texttt{throw}\)문이 실행되면 Unwinding Stack(스택 풀기)가 수행되면서,
Callee에서 Caller의 \(\texttt{catch}\) Block으로 제어가 옮겨진다.
(\(\texttt{catch}\) Block에서 사용하여 예외를 재발생시켜, 해당 함수를 한 층 더 벗어나게 한다.)
throw;
Keyword: \(\texttt{catch}\)
- Exception Handler(포착한 예외에 대한 처리방법)을 기술한다.
(Exception Handler = \(\texttt{catch}\) Block)
- \(\texttt{throw}\) 키워드로부터 넘겨받은 객체의 데이터 타입을 소괄호 안에 명시해야 한다.
(\(\texttt{throw}\)가 넘긴 객체의 데이터 타입과 \(\texttt{catch}\)에서 명시한 데이터 타입이 일치해야 해당 Exception Handler가 동작한다.)
- \(\texttt{catch}\) 키워드는 예외 발생 시, 프로그램 제어를 넘겨받기 위한 레이블의 역할을 한다.
(즉, \(\texttt{catch}\) Block은 함수가 아니다.)
catch (const char* s) {
cout << s << std::endl;
...
}
※ \(\texttt{catch}\) Block에서는 \(\texttt{throw}\)로 부터 객체의 복사본을 넘겨받는다.
- \(\texttt{catch}\) Block의 데이터형을 참조로 선언한다 해도, 복사본을 넘겨받게 된다.
- 객체가 해당 Scope를 넘어가서 전달되기 전에 소멸되는 것을 방지하기 위함이다.
- 그럼에도 불구하고, 참조 선언을 가능하게 한 이유는 상속 관계에 있는 객체를 모두 아우를 수 있게하기 위함이다.*
* 참조형으로 선언된 Base Class Object는 Derived Class Object도 참조할 수 있다. (역은 성립하지 않는다.)
※ 각각의 상속 레벨에 대한 \(\texttt{catch}\) Block이 수행할 작업을 모두 다르게 지정하고자 하는 경우,
아래 코드와 같이, \(\texttt{catch}\) Block을 상속 계층의 역순으로 정의해야 한다.
(기초 클래스 객체는 모든 파생 클래스 객체를 아우를 수 있지만, 파생 클래스 객체는 기초 클래스 객체를 받을 수 없기 때문이다.)
class bad_1 {...};
class bad_2 : public bad_1 {...};
class bad_3 : public bad_2 {...};
...
void duper() throw (bad_1) {
if(specific_cond_1)
throw bad_1();
if(specific_cond_2)
throw bad_2();
if(specific_cond_3)
throw bad_3();
}
...
try {
duper();
}
catch(bad_3& B) { ... } // 말단 파생 클래스에 대한 catch Block
catch(bad_2& B) { ... } // 중간 파생 클래스에 대한 catch Block
catch(bad_1& B) { ... } // 기초 클래스에 대한 catch Block
// 상속 레벨에 따라 catch의 동작 내용을 달리하고자 한다면,
// 위와 같이, 상속 관계의 최하단에 있는 파생 클래스에 대한 catch문부터 작성해야 한다.
※ - 넘겨받을 객체의 데이터 타입 대신, ELIPSIS(...)를 명시하여, 모든 예외를 처리하게 할 수 있다.
try {
throw specific_object_1();
}
catch(bad_3& be) { ... }
catch(bad_2& be) { ... }
catch(bad_1& be) { ... }
catch(...) { } // 괄호 안에 "..."은 ELIPSIS를 의미하는 C++예약어이다. 모든 예외를 가리지 않고 받아들인다.
// switch문의 default문과 유사한 기능을한다.
// 즉, 예외가 bad_3, bad_2, bad_1에도 해당되지 않으면 이 catch문이 동작하게 된다.
Keyword: \(\texttt{try}\)
- \(\texttt{try}\) 키워드로 정의된 \(\texttt{try}\) Block은 예외들이 발생할 수 있는 가능성이 있는 코드들이 위치하는 코드 블럭이다.
- \(\texttt{try}\) 블럭 뒤에는 하나 이상의 Exception Handler(\(\texttt{catch}\) Block)이 뒤따르게 된다.
- \(\texttt{try}\) 블럭에서 예외가 발생하지 않았다면, 뒤따르는 \(\texttt{catch}\) 블럭들은 모두 무시된다.
try {
c = division(a, b); // a / b에서 b가 0이면 예외가 발생한 것으로 처리하는 것이 바람직할 것이다.
...
}
// Harmonic Mean (조화 평균) 계산 프로그램
// C++ 예외 처리 메커니즘(try - throw - catch)을 이용하여 예외상황에 대처하는 코드
#include <iostream>
double hmean(double a, double b);
int main() {
double x, y, z;
std::cout << "Enter two numbers: ";
while (std::cin >> x >> y) {
try {
// start of try block
z = hmean(x,y);
} // end of try block
catch (const char * s) {
// start of exception handler
std::cout << s << std::endl;
std::cout << "Enter a new pair of numbers: ";
continue; // catch 블록은 함수가 아니므로, 이와 같이 while문에 대한 continue문을 사용할 수 있다.
} // end of handler
std::cout << "Harmonic mean of " << x << " and " << y << " is " << z << std::endl;
std::cout << "Enter next set of numbers <q to quit>: ";
}
std::cout << "Bye!\n";
return 0;
}
double hmean(double a, double b) {
if (a == -b)
throw "bad hmean() arguments: a = -b not allowed";
return 2.0 * a * b / (a + b);
}
※ \(\texttt{throw}\)문에 일치하는 데이터 타입을 가진 \(\texttt{catch}\) Block이 없거나, \(\texttt{try}\) Block이 없으면, \(\texttt{abort()}\) 함수가 호출된다.
※ \(\texttt{return}\)문과 \(\texttt{throw}\)문의 차이
Using Objects as Exceptions (예외로써 객체 사용하기)
- 예외로써 서로 다른 데이터 타입을 사용하여 예외가 발생된 상황을 구별할 수 있다.
- 또한, 객체 자체에 예외 사항에 대한 정보를 담아 넘길 수도 있다.
// Harmonic Mean (조화 평균), geometric Mean (기하 평균) 계산 프로그램
// C++ 예외 처리 메커니즘(try - throw - catch)을 이용하여 예외상황에 대처하는 코드
// 예외로써 객체를 넘긴다.
// exc_mean.h -- exception classes for hmean(), gmean()
#include <iostream>
class bad_hmean {
private:
double v1;
double v2;
public:
bad_hmean(double a = 0, double b = 0) : v1(a), v2(b){}
void mesg();
};
inline void bad_hmean::mesg() {
std::cout << "hmean(" << v1 << ", " << v2 <<"): " << "invalid arguments: a = -b\n";
}
class bad_gmean {
public:
double v1;
double v2;
bad_gmean(double a = 0, double b = 0) : v1(a), v2(b){}
const char * mesg();
};
inline const char * bad_gmean::mesg() {
return "gmean() arguments should be >= 0\n";
}
// ---------------------------------------------------------------------
//error4.cpp – using exception classes
#include <iostream>
#include <cmath> // or math.h, unix users may need -lm flag
#include "exc_mean.h"
double hmean(double a, double b);
double gmean(double a, double b);
int main() {
using std::cout;
using std::cin;
using std::endl;
double x, y, z;
cout << "Enter two numbers: ";
while (cin >> x >> y) {
try { // start of try block
z = hmean(x,y);
cout << "Harmonic mean of " << x << " and " << y << " is " << z << endl;
cout << "Geometric mean of " << x << " and " << y << " is " << gmean(x,y) << endl;
cout << "Enter next set of numbers <q to quit>: ";
}// end of try block
catch (bad_hmean & bg) { // start of catch block
bg.mesg();
cout << "Try again.\n";
continue;
}
catch (bad_gmean & hg) {
cout << hg.mesg();
cout << "Values used: " << hg.v1 << ", " << hg.v2 << endl;
cout << "Sorry, you don't get to play any more.\n";
break;
} // end of catch block
}
cout << "Bye!\n";
return 0;
}
double hmean(double a, double b) {
if (a == -b)
throw bad_hmean(a,b);
return 2.0 * a * b / (a + b);
}
double gmean(double a, double b) {
if (a < 0 || b < 0)
throw bad_gmean(a,b);
return std::sqrt(a * b);
}
// ---------------------------------------------------------------------
Exception Specifications Meet C++11 (C++11에서의 예외 사양)
- C++98에서 추가되었으며 C++11에 접어서면서 그 중요성이 떨어지고 있는 개념이다.
※ 이 개념은 추후, 표준에서 제외될 가능성이 있으므로, 가급적 사용을 자제해야 한다.
void func_1(int n) throw(bad_thing);
// 함수 func_1에서는 bad_thing 타입의 예외가 발생할 가능성이 있다.
void func_2(int n) throw();
// 함수 func_2에서는 예외를 발생시키지 않으나, 예외를 발생시키는 함수를 호출할 가능성이 있다.
void func_3(int n) noexcept;
// 함수 func_3는 예외를 발생시키지 않는다.
- 위와 같은 함수 이름 끝의 throw() 문은 함수 원형 혹은 함수 정의부에서 기술될 수 있다.
- 사용자게에 try Block을 사용할 필요가 있음을 알리기 위한 명시적인 표현방법이다.
- 또한, 컴파일러가 런타임 체크용 코드를 추가하여 예외 사항이 위반되었는지에 대한 여부를 확인할 수 있게 한다.
* \(\texttt{noexcept(operand)}\) Operator
- operand가 예외를 발생시킬 수 있는지에 대한 여부를 알리는 연산자이다.
- operand가 예외를 발생하지 않는다는 보장이 있으면 \(\texttt{true}\)를 리턴하고, 그렇지 않으면 \(\texttt{false}\)를 리턴한다.
void func_A(int n);
void func_B(int n) noexcept;
...
noexcept(func_A); // false를 리턴한다.
noexcept(func_B); // true를 리턴한다.
\(\texttt{exception}\) Class (\(\texttt{exception}\) 클래스)
- \(\texttt{exception}\) 클래스에 정의된 표준 예외는 아래와 같다.
- \(\texttt{bad_alloc}\)
- \(\texttt{bad_cast}\)
- \(\texttt{bad_exception}\)
- \(\texttt{bad_typeid}\)
- \(\texttt{bad_function_call}\)
- \(\texttt{bad_weak_ptr}\)
- \(\texttt{logic_error}\)
- \(\texttt{runtime_error}\)
- \(\texttt{exception}\) 헤더파일 (\(\texttt{exception.h}\) 또는 \(\texttt{except.h}\))에 정의되어 있다.
- \(\texttt{exception}\)클래스는 \(\texttt{std}\) namespace내에 분류되어 있다.
- \(\texttt{exception}\) 헤더파일에서는 \(\texttt{unexpected()}\) 함수에 의해 \(\texttt{bad_exception}\)을 사용할 수 있다.
※ 클래스 정의 시, \(\texttt{exception}\) 클래스를 상속받아서 \(\texttt{exception}\) 객체에 대한 \(\texttt{catch}\) Block을 이용해 모든 파생 객체들에 대한 예외를 한꺼번에 처리할 수 있다.
#include <exception>
class bad_hmean : public std::exception {
public:
const char * what() { return "bad arguments to hmean()"; }
...
};
class bad_gmean : public std::exception {
public:
const char * what() { return "bad arguments to gmean()"; }
...
};
try { ... }
catch(std::exception & e) {
cout << e.what() << endl;
...
}
* \(\texttt{what()}\) 메서드
- 발생한 Exception 객체의 종류를 문자열로 리턴하는, \(\texttt{exception}\) 클래스에 정의된 메서드이다.
※ 예외는 하나의 클래스이다.
- 하나의 예외 클래스를 다른 클래스로부터 파생시킬 수 있다.
- 클래스 정의 안에 예외 클래스 선언을 내포시켜 예외를 클래스에 병합시킬 수 있다.
- 내포된 예외들 또한 상속될 수 있으며, 기초 클래스의 역할을 할 수 있다.
* 예외 클래스를 내포시킨 클래스 예시
#include <stdexcept>
#include <string>
using std::string;
class Sales
{
public:
enum {MONTHS = 12}; // could be a static const
class bad_index : public std::logic_error
{ // 배열 인덱스 범위를 벗어난 값을 저장하는 클래스
// 클래스 외부에서는 Sales::bad_index
// 클라이언트 프로그램의 catch 블록에서 bad_index를 하나의 예외 객체로 사용할 수 있다.
private:
int bi; // bad index value
public:
explicit bad_index(int ix,
const std::string & s = "Index error in Sales object\n");
int bi_val() const {return bi;}
virtual ~bad_index() throw() {}
};
explicit Sales(int yy = 0);
Sales(int yy, const double * gr, int n);
virtual ~Sales() { }
int Year() const { return year; }
virtual double operator[](int i) const;
virtual double & operator[](int i);
private:
double gross[MONTHS];
int year;
};
Sales::bad_index::bad_index(int ix, const string & s )
: std::logic_error(s), bi(ix) { }
Sales::Sales(int yy)
{
year = yy;
for (int i = 0; i < MONTHS; ++i)
gross[i] = 0;
}
Sales::Sales(int yy, const double * gr, int n)
{
year = yy;
int lim = (n < MONTHS)? n : MONTHS;
int i;
for (i = 0; i < lim; ++i)
gross[i] = gr[i];
// for i > n and i < MONTHS
for ( ; i < MONTHS; ++i)
gross[i] = 0;
}
double Sales::operator[](int i) const
{
if(i < 0 || i >= MONTHS)
throw bad_index(i);
return gross[i];
}
double & Sales::operator[](int i)
{
if(i < 0 || i >= MONTHS)
throw bad_index(i);
return gross[i];
}
class LabeledSales : public Sales
{
public:
class nbad_index : public Sales::bad_index
{ // bad_index로부터 파생되며, LabeledSales 객체의 레이블을 저장 및 보고하는 기능이 추가되었다.
// 클래스 외부에서는 LabeledSales::nbad_index
private:
std::string lbl;
public:
nbad_index(const std::string & lb, int ix, const std::string & s = "Index error in LabeledSales object\n");
const std::string & label_val() const {return lbl;}
virtual ~nbad_index() throw() {}
};
explicit LabeledSales(const std::string & lb = "none", int yy = 0);
LabeledSales(const std::string & lb, int yy, const double * gr, int n);
virtual ~LabeledSales() { }
const std::string & Label() const {return label;}
virtual double operator[](int i) const;
virtual double & operator[](int i);
private:
std::string label;
};
LabeledSales::nbad_index::nbad_index(const string & lb, int ix, const string & s )
: Sales::bad_index(ix, s)
{
lbl = lb;
}
LabeledSales::LabeledSales(const string & lb, int yy)
: Sales(yy)
{
label = lb;
}
LabeledSales::LabeledSales(const string & lb, int yy, const double * gr, int n)
: Sales(yy, gr, n)
{
label = lb;
}
double LabeledSales::operator[](int i) const
{
if(i < 0 || i >= MONTHS)
throw nbad_index(Label(), i);
return Sales::operator[](i);
}
double & LabeledSales::operator[](int i)
{
if(i < 0 || i >= MONTHS)
throw nbad_index(Label(), i);
return Sales::operator[](i);
}
// use_sales.cpp -- nested exceptions
#include <iostream>
#include "sales.h"
int main()
{
using std::cout;
using std::cin;
using std::endl;
double vals1[12] =
{
1220, 1100, 1122, 2212, 1232, 2334,
2884, 2393, 3302, 2922, 3002, 3544
};
double vals2[12] =
{
12, 11, 22, 21, 32, 34,
28, 29, 33, 29, 32, 35
};
Sales sales1(2011, vals1, 12);
LabeledSales sales2("Blogstar",2012, vals2, 12 );
cout << "First try block:\n";
try
{
int i;
cout << "Year = " << sales1.Year() << endl;
for (i = 0; i < 12; ++i)
{
cout << sales1[i] << ' ';
if (i % 6 == 5)
cout << endl;
}
cout << "Year = " << sales2.Year() << endl;
cout << "Label = " << sales2.Label() << endl;
for (i = 0; i <= 12; ++i)
{
cout << sales2[i] << ' ';
if (i % 6 == 5)
cout << endl;
}
cout << "End of try block 1.\n";
}
catch(LabeledSales::nbad_index & bad)
{
cout << bad.what();
cout << "Company: " << bad.label_val() << endl;
cout << "bad index: " << bad.bi_val() << endl;
}
catch(Sales::bad_index & bad)
{
cout << bad.what();
cout << "bad index: " << bad.bi_val() << endl;
}
cout << "\nNext try block:\n";
try
{
sales2[2] = 37.5;
sales1[20] = 23345;
cout << "End of try block 2.\n";
}
catch(LabeledSales::nbad_index & bad)
{
cout << bad.what();
cout << "Company: " << bad.label_val() << endl;
cout << "bad index: " << bad.bi_val() << endl;
}
catch(Sales::bad_index & bad)
{
cout << bad.what();
cout << "bad index: " << bad.bi_val() << endl;
}
cout << "done\n";
return 0;
}
※ C++98까지는 Destructor에 throw() 예외를 사용할 수 있었다.
※ C++11부터는 Destructor에 throw() 예외를 사용할 수 없게 되었다.
\(\texttt{stdexcept}\) Header File (\(\texttt{stdexcept}\) 헤더파일)
- \(\texttt{stdexcept}\) 헤더파일에는 \(\texttt{logic_error}\) Exception Family, \(\texttt{runtime_error}\) Exception Family가 정의되어 있다.
- 이 두 Exception Family는 \(\texttt{exception}\)을 \(\texttt{public}\)으로 상속받은 클래스이다.
\(\texttt{logic_error}\) Exception Class Family
- 프로그램 수정을 통해 극복이 가능한것으로 판단되는 예외들로 구성된다.
- 아래 4개의 예외는 \(\texttt{logic_error}\)에 속하는 예외들이다.
- \(\texttt{domain_error}\) Class : 기대한 범위를 넘어선 값이 함수에 전달되었을 때 예외를 발생시킨다.
- \(\texttt{invalid_argument}\) Class : 기대하지 않은 형태의 값이 함수에 전달되었을 때 예외를 발생시킨다.
- \(\texttt{length_error}\) Class : 원하는 액션을 취할 만한 충분한 공간이 확보되지 못했을 때 예외를 발생시킨다.
ex) \(\texttt{string}\) 클래스의 \(\texttt{append()}\)메서드는 특정 문자열이 최대 허용 길이를 초과했을 때 \(\texttt{length_error}\)를 발생시킨다.
- \(\texttt{out_of_bounds}\) Class : 인덱싱 에러를 감지하면 예외를 발생시킨다.
\(\texttt{runtime_error}\) Exception Class Family
- 불가피한 예외들로 구성된다.
- 아래 3개의 예외는 \(\texttt{runtime_error}\)에 속하는 예외들이다.
- \(\texttt{range_error}\) Class : 오버/언더플로우가 발생하지 않은 상태에서 치역범위를 넘어섰을 때, 예외를 발생시킨다.
- \(\texttt{overflow_error}\) Class : 오버플로우가 발생되었을 때, 예외를 발생시킨다.
- \(\texttt{underflow_error}\) Class : 언더플로우가 발생되었을 때, 예외를 발생시킨다.
※ C++ 상속 메커니즘에 따라, 계층적으로 Exception Class Family를 아우르는 \(\texttt{catch}\) Block 또한 충분히 작성 가능하다.
try {
...
}
catch(out_of_bounds & oe) // catch out_of_bounds error
{...}
catch(logic_error & oe) // catch remaining logic_error family
{...}
catch(exception & oe) // catch runtime_error, exception objects
{...}
bad_alloc Exception
- C++ 시스템에서 메모리 할당에 문제가 발생한 경우에 발생시키는 예외이다.
- bad_alloc 클래스는 exception 클래스를 public으로 상속받은 클래스이다.
- bad_alloc 클래스는 new 헤더파일 (또는 new.h)에 정의되어 있다.
* C++에서는 메모리 할당 시, 문제가 발생했을 때 두 가지 방법으로 대처한다.
1. new가 null을 리턴한다.
2. bad_alloc 예외를 발생시킨다.
- 컴파일러 스위치 등과 같은 방법을 통해 둘 중 하나를 Default로 세팅할 수 있다.
* 예외 발생 시, new가 null을 리턴하도록 하는 Flag 설정법
- new에 nothrow를 설정한다.
int* pi = new(std::nothrow) int;
if(pi == 0){
cout << "Rejected Allocation" << std::endl;
...
}
* bad_alloc 핸들링 예시
// newexcp.cpp -- the bad_alloc exception
#include <iostream>
#include <new>
#include <cstdlib> // for exit(), EXIT_FAILURE
using namespace std;
struct Big
{
double stuff[20000];
};
int main()
{
Big* pb;
try {
cout << "Trying to get a big block of memory:\n";
pb = new Big[1000000000];
cout << "Got past the new request:\n";
}
catch (bad_alloc& ba)
{
cout << "Caught the exception!\n";
cout << ba.what() << endl;
exit(EXIT_FAILURE);
}
cout << "Memory successfully allocated\n";
pb[0].stuff[0] = 4;
cout << pb[0].stuff[0] << endl;
delete[] pb;
return 0;
}
Unexpected Exception (기대하지 않은 예외)
- throw()나 noexcept와 같은 Exception Specification(예외 사양)에 지정되어 있지 않은 곳에서 예외가 발생한 경우를 의미한다.
- 감지되면, 프로그램 실행을 중지시킨다.
- 함수에 Exception Specification(예외 사양, 예외 지정)을 사용함으로써, 해당 함수를 사용하는 사용자들은 그 함수에서 어떤 예외들이 발생할 수 있는지를 사전에 알 수 있다.
double Argh(double, double) throw(out_of_bound);
// 여기서 throw(out_of_bound) 구문이 예외 사양에 해당된다.
// Argh() 함수에서는 out_of_bound 타입의 예외가 발생될 수 있음을 의미한다.
try {
x = Argh(a, b);
}
catch (out_of_bound& ex) { // Argh() 함수에서 발생할 수 있는 out_of_bound 예외에 대비한다.
...
}
※ 원칙적으로, 예외 지정에는 해당 함수에서 발생될 수 있는 모든 예외들이 명시되어야 한다.
- 가령, A() 함수에서는 a라는 예외가 발생할 수 있고, B() 함수에서는 b라는 예외가 발생할 수 있으며, A() 함수에서 B() 함수를 호출한다고 하면, A() 함수에서의 예외 지정에는 a와 b에 대한 예외 지정이 모두 이루어져야 한다는 뜻이다.
- 이는 예외 규격 메커니즘이 좁은 범뮈내에서 사용되어야 함을 의미한다.
- 이러한 점으로 인해, C++11에서 예외 규격(예외 지정) 개념을 제외하고자 하는 것이다.
* Unexpected Exception가 발생하면, 프로그램은 unexcepted() 함수를 호출한다.
- unexcepted() 함수는 기본적으로 terminate() 함수를 호출하고,
terminate() 함수는 기본적으로 abort() 함수를 호출한다.
- set_unexcepted() 함수를 통해, unexcepted() 함수의 동작을 변경할 수 있다.
- 특히, set_unexcepted() 함수는 set_terminate() 함수보다 더욱 세밀하게 제어할 수 있다.
- 위 함수들 모두 exception 헤더파일에 선언되어 있다.
typedef void (*unexpected_handler)();
unexpected_handler set_unexpected(unexpected_handler f) throw(); // C++98
unexpected_handler set_unexpected(unexpected_handler f) noexcept; // C++11
void unexpected(); // C++98
void unexpected() noexcept; // C++0x
* unexcepted_handler 함수는 아래와 같은 기능을 할 수 있다.
1. terminate() 함수(디폴트), abort() 함수, exit() 함수로 대체되어 프로그램을 종료시킬 수 있다.
2. 예외를 발생시킬 수 있다.
- 발생한 예외가 예외 지정에 일치하면, 그에 대응되는 catch Block을 찾는다.
- 발생한 예외가 예외 지정에 일치하지는 않으나, 예외 지정에 std::bad_exception형이 명시되어 있으면,
해당 예외는 bad_exception형 예외로 간주된다.
- 발생한 예외가 예외 지정에 일치하지 않고, 예외 지정에 std::bad_exception형조차 명시되어 있지 않으면,
프로그램은 terminate() 함수를 호출한다.
* unexcepted_handler 함수가 예외를 발생시키고, 이를 적절히 처리하는 구조
#include <exception> // 예외에 관련된 여러가지가 정의되어 있다.
void myUnexcepted() {
throw std::bad_exception(); // throw;문만 적어도 무방하다.
}
...
set_unexcepted(myUnexpected);
...
double Argh(double, double) throw(out_of_bounds, bad_exception);
...
try {
x = Argh(a, b);
}
catch (out_of_bound& ex) {
// out_of_bound형 예외에 대한 대처사항
}
catch (bad_exception& ex) {
// bad_exception형 예외에 대한 대처사항
}
Uncaught Exception (포착되지 않은 예외)
- try Block 밖에서 발생되거나, 어떠한 catch Handler와도 매치되지 않는 예외가 발생된 경우를 의미한다.
- 감지되면 terminate() 함수를 호출하며, 프로그램을 즉각 중단시키지 않는다.
- terminate() 함수는 기본적으로 abort() 함수를 호출하며, abort() 함수 대신, 다른 함수를 호출하도록 변경할 수 있다.
- terminate() 함수가 호출하는 함수는 set_terminate() 함수를 이용하여 바꿀 수 있다.
- terminate() 함수와 set_terminate() 함수는 exception 헤더파일에 선언되어 있다.
typedef void (*terminate_handler)();
// terminate_handler : 매개변수를 사용하지 않고, 리턴값도 없는 함수를 가리키는 포인터를 위한 데이터형
terminate_handler set_terminate(terminate_handler f) throw(); // C++98
terminate_handler set_terminate(terminate_handler f) noexcept; // C++11
// set_terminate() 함수는
// 매개변수를 사용하지 않고 리턴형이 void인 함수(terminate_handler 타입)의 이름(=주소)을 매개변수로 사용한다.
// 이전에 등록된 함수의 주소를 리턴한다.
// set_terminate() 함수가 한 번 이상 호출되면,
// terminate() 함수는 가장 최근의 set_terminate() 함수 호출에 의해 설정된 함수를 호출한다.
void terminate(); // C++98
void terminate() noexcept; // C++11
// 예외가 발생했으나, 포착되지 않으면, 프로그램은 terminate() 함수를 호출한다.
// terminate() 함수는 기본적으로 abort() 함수를 호출하게 되어있다.
// set_terminate() 함수를 이용하여 terminate() 함수가 abort() 함수 대신, 다른 함수를 호출하게 할 수 있다.
* 시스템은 Uncaught Exception가 발생하면, terminate() 함수를 호출하고,
사용자의 의도에 따라 terminate() 함수는 myQuit() 함수를 호출한다.
void myQuit() {
std::cout << "포착되지 않은 예외가 발생하여 프로그램을 중지시킵니다.\n";
exit(5);
}
...
set_terminate(myQuit); // 프로그램의 시작 부분에 위치해야 한다.
Exception Cautions (예외 주의사항)
* 예외의 단점
- 예외를 사용함으로써 프로그램의 크기가 커지고, 실행속도를 저해한다.
- 예외 지정은 템플릿 개념과 어울리지 않는다.
(템플릿을 통한 여러 종류의 특수화로 인해 여러 종류의 예외가 발생될 수 있기 때문이다.
즉, 템플릿 기능은 발생될 예외의 예측을 어렵게 하기 때문이다.)
- 예외는 동적 메모리 할당 개념과 어울리지 않는다.
(메모리 반납이 이루어지기 전에, 예외가 발생되어 throw문이 실행되고 프로그램이 종료되면,
반납되지 못한 메모리로 인한 Memory Leak이 발생한다.
void test2(int n) {
double* ar = new double[n];
...
if (oh_no)
throw exception(); // 메모리 반납 이전에 예외가 발생되면, throw문으로 인해 프로그램이 종료된다.
...
delete[] ar; // 메모리 반납
return;
}
* 프로그래머가 Memory Leak에 대비하여, catch Block에 따로 메모리 반납 코드를 삽입함으로써,
예외 처리로 인한 Memory Leak을 방지할 수 있다.
void test3(int n) {
double * ar = new double[n];
...
try {
if (oh_no)
throw exception();
}
catch(exception & ex)
{
delete [] ar; // 예외가 발생한 경우의 메모리 해제 코드
throw;
}
...
delete [] ar; // 예외가 발생되지 않은 경우의 메모리 해제 코드
return;
}
- 이 방법은 프로그래머의 실수로 해제를 빠뜨리거나, 다른 에러를 발생시킬 가능성이 있어 완벽한 해결책이라 할 수 없다.
- 이에 대한 해결책으로, auto_ptr 템플릿이 있다.