예외 처리 Exception
C++ Standard Libary에서 제공하는 예외 처리를 위한 클래스로 자신의 프로그램 코드에서 어느 부분에서 예외가 발생할 것을 대비해 미리 예외 발생에 대한 디버깅 정보를 알기 위해 사용한다.
주로 어떤 모듈의 초기화 중 발생한 소프트웨어 에러나 메모리 할당/해제, 혹은 어떤 특정 코드 범위 내에서 발생하는 예외만 관찰하고 싶을 때 사용한다.
VC 런타임 헤더 vcruntime_exception.h를 보면 예외 처리 Exception 클래스에 대한 정보가 있다.
namespace std {
#pragma warning(push)
#pragma warning(disable: 4577) // 'noexcept' used with no exception handling mode specified
class exception
{
public:
exception() noexcept
: _Data()
{
}
explicit exception(char const* const _Message) noexcept
: _Data()
{
__std_exception_data _InitData = { _Message, true };
__std_exception_copy(&_InitData, &_Data);
}
exception(char const* const _Message, int) noexcept
: _Data()
{
_Data._What = _Message;
}
exception(exception const& _Other) noexcept
: _Data()
{
__std_exception_copy(&_Other._Data, &_Data);
}
exception& operator=(exception const& _Other) noexcept
{
if (this == &_Other)
{
return *this;
}
__std_exception_destroy(&_Data);
__std_exception_copy(&_Other._Data, &_Data);
return *this;
}
virtual ~exception() noexcept
{
__std_exception_destroy(&_Data);
}
_NODISCARD virtual char const* what() const
{
return _Data._What ? _Data._What : "Unknown exception";
}
private:
__std_exception_data _Data;
};
특징을 보자면,
- 복사 생성 / 대입 생성을 제공하고 기본 생성자와 const char* 문자열을 받는 생성자가 있다.
- 문자열의 내용으로 throw할 예외에 대한 내용을 전달하여 Exception 인스턴스를 생성한다.
- 내부에 __std_exception_data 구조체를 저장하고 있는데, 그 내용을 보면 _What이라는 문자열을 따로 저장해 두고 있다.
struct __std_exception_data
{
char const* _What;
bool _DoFree;
};
_What은 실제 what()이 호출될 때 저장해두었던 문자열을 출력하기 위한 버퍼용으로 쓰이고 있다.
보통의 예외 처리의 로직은 Excpetion 클래스 인스턴스를 생성하자마자 throw 해준다.
class ViewApplication
{
public:
ViewApp(){}
bool init()
{
// 디바이스 생성 Configuration
DeviceInfo dInfo = {}
dInfo.osType = WINDOW;
dInfo.memType = DEFAULT_MEM;
...
// 디바이스 초기화
if(!InitDevice(&dInfo, &display))
return false;
return true;
}
Device display;
}
void Program(int num)
{
ViewAppliation app;
if(!app.init())
{
// throw custom standard excpetion
throw std::exception("App initialization failed");
}
if(num >= 10)
{
// throw out_of_range which is subclass of standard exception
throw out_of_range("out of range in array a");
}
int a[10] = 0;
for(int i=0;i<num;i++)
{
a[i] = i;
}
}
int main()
{
try()
{
Program(10);
}
catch(const std::exception& e)
{
cout << "Standard Exception" << e.what() << endl;
}
catch(...)
{
cout << "Unknown Excpetion" << endl;
}
return 0;
}
위 코드의 문제는 out_of_range 인스턴스나 std::exception("App initialization failed")의 exception 인스턴스나 같은 std::exception 클래스이기 때문에 예외 처리가 구분이 되지 않는다.
MyException 클래스
std::exception 클래스를 상속받아 새로운 예외 처리 클래스를 정의하고 ViewApplication 인스턴스를 초기화가 실패할 때 예외 처리를 하여 "실패 상황을 구분하도록" 다음과 같이 이 MyException 인스턴스를 catch 해보는 것으로 구현한다.
int main()
{
try()
{
Program(10);
}
catch(const MyException& e)
{
cout << "MyException Exception" << e.what() << endl;
}
catch(const std::exception& e)
{
cout << "Standard Exception" << e.what() << endl;
}
catch(...)
{
cout << "Unknown Excpetion" << endl;
}
return 0;
}
클래스 예시
class MyException : public exception
{
protected:
mutable string m_whatBuffer;
private:
int erCodeLine;
string erFileName;
string erMsg;
public:
MyException(int codeLine, const char* fileName, const char* message) noexcept;
// 예외 발생시 상세 내용 출력 인터페이스
virtual const char* what() const noexcept final;
virtual const char* GetType() const noexcept;
virtual string GetErrorMessage() const noexcept;
};
예외 처리는 실제 예외가 발생한 코드 라인과 실행 파일 이름을 받아 생성하고 what()을 호출할 시 이 내용을 출력해주도록 한다.
what()의 기존 인터페이스를 final로 설정하여 추후 MyException을 상속받는 클래스가 GetType()과 GetErrorMessage()를 오버라이드 구현을 하여 what()을 통해 재사용 가능했으면 좋겠다. GetType() GetErrorMessage() 등으로 정의해주었는데 이런 설계는 자유롭게 할 수 있다.
구현 예시
MyExcption::MyExcption(int codeLine, const char* fileName, const char* message) noexcept
:
erCodeLine(codeLine),
erFileName(fileName),
erMsg(message)
{
}
const char* MyExcption::what() const noexcept
{
ostringstream oss;
oss << "[Type] " << GetType() << endl
<< GetErrorMessage() << endl
<< erMsg << endl;
m_whatBuffer = oss.str();
return m_whatBuffer.c_str();
}
const char* MyExcption::GetType() const noexcept
{
return "My Exception";
}
string MyExcption::GetErrorMessage() const noexcept
{
ostringstream oss;
oss << "[File] " << erFileName << endl
<< "[Line] " << erCodeLine;
return oss.str();
}
- what buffer의 필요성
what()을 호출 할 때 내부에서 예외 발생 내용에 관한 문자열을 전달해주어야 한다. 이 전달 과정은 printf("안녕하세요");의 원리, "안녕하세요"라는 문자열을 콘솔 출력 스트림으로 전달하는 printf 함수와 마찬가지로 예외 처리 다이얼 로그에 표시되는 문자열을 찍어내는 "상상 함수" ExceptionDialog("안녕하세요");와 동일한 원리를 가진다.
const char*의 문자열, 즉 문자 배열의 포인터를 전달하는데 포인터의 실제 내용이 함수 내 지역 변수였다면 함수를 빠져나오면서 해제되므로 실제 내용은 쓰레기 값이 된다.
ex)
const char* mystring()
{
string yeah = "yeah~";
return yeah.c_str();
}
printf(mystring());
물론 간혹 릴리즈 모드로 빌드 시 해당 문자열 내용에 대해 c_str()를 인라인화하여 printf("yeah~");처럼 만들어 정상적으로 나오기도 한다.
대체적으로 다음과 같이 what 버퍼를 사용하지 않는다면,
const char* UserException::what() const noexcept
{
ostringstream oss;
oss << "[Type] " << GetType() << endl
<< GetErrorMessage() << endl;
// m_whatBuffer = oss.str();
return oss.str().c_str();
}
아래와 같은 이상한 문자열을 가진 예외 발생 다이얼 로그가 나오게 된다.
std::exception 클래스는 생성을 할 때 문자열을 whatbuffer로 저장해두었고 구현 예제에서는 새로운 문자열을 스트링 스트림으로 새로 만들어 주기 때문에 what buffer가 꼭 필요한 상황이다.
구현 예시처럼 정상 동작을 한다면 다음과 같이 예외 발생 다이얼로그가 생성된다.
매크로화 한 MyException 사용 예시
#define MY_EXP(MSG) MyException(__LINE__, __FILE__, MSG); 로 코드 라인과 파일 이름을 가리키는 C++ Predefined Macro를 이용한다면 간단히 예외 발생을 할 수 있다.
C++ Predefined Macro: riptutorial.com/cplusplus/example/4867/predefined-macros
#include "MyExcpetion"
#define MY_EXP(MSG) MyException(__LINE__, __FILE__, MSG);
class ViewApplication
{
public:
ViewApp(){}
bool init()
{
// 디바이스 생성 Configuration
DeviceInfo dInfo = {}
dInfo.osType = WINDOW;
dInfo.memType = DEFAULT_MEM;
...
// 디바이스 초기화
if(!InitDevice(&dInfo, &display))
return false;
return true;
}
Device display;
}
void Program(int num)
{
ViewAppliation app;
if(!app.init())
{
// throw custom standard excpetion
throw MY_EXP("App initialization failed");
}
if(num >= 10)
{
// throw out_of_range which is subclass of standard exception
throw out_of_range("out of range in array a");
}
int a[10] = 0;
for(int i=0;i<num;i++)
{
a[i] = i;
}
}
int main()
{
try()
{
Program(10);
}
catch(const MyException& e)
{
cout << e.what() << endl;
}
catch(const std::exception& e)
{
cout << "Standard Exception" << e.what() << endl;
}
catch(...)
{
cout << "Unknown Excpetion" << endl;
}
return 0;
}
'Advanced C++' 카테고리의 다른 글
[C++] 함수 객체 Functor, std::function, std::bind (0) | 2021.02.22 |
---|---|
[C++] 가변 길이 템플릿 Variadic Template (0) | 2021.02.21 |
[C++ 클래스] 순수 가상 소멸자 (0) | 2021.02.18 |
[C++ 클래스] 가상 함수 테이블 (0) | 2021.01.29 |
[C++ 클래스] 가상 함수 (0) | 2021.01.29 |