티스토리 뷰
int _tmain(int argc, _TCHAR* argv[]) { FILE* fp; fp = fopen("test.bin", "rb"); if(fp == NULL) { // 예외 처리 return 1; } char buffer[512] = {0, }; fread(buffer, 512, 1, fp); return 0; }대부분 위 코드처럼 작성하게 됩니다. 위 코드에서 누락된 예외처리 발견하신분? 예. 바로 fread 부분입니다.
int _tmain(int argc, _TCHAR* argv[]) { FILE* fp; fp = fopen("test.bin", "rb"); if(fp == NULL) { // 예외 처리 return 1; } char buffer[512] = {0, }; int read_count = fread(buffer, 512, 1, fp); if(read_count == 0) { // 예외 처리 return 2; } return 0; }이렇게 되어야 buffer의 데이터가 유효하게 로직으로 전달되었는지 검사할 수 있습니다.
int _tmain(int argc, _TCHAR* argv[]) { try { FILE* fp; fp = fopen("test.bin", "rb"); if(fp == NULL) { throw std::exception("file open failed."); } char buffer[512] = {0, }; int read_count = fread(buffer, 512, 1, fp); if(read_count == 0) { throw std::exception("file data read failed."); } } catch(const std::exception& e) { std::cout << e.what() << std::endl; return 1; } return 0; }사용자 정의 exception 클래스를 이용할 수도 있지만, 여기선 쉬운 예제를 위해 std::exception 클래스를 통해 예외처리를 했습니다. 사실 이렇게 간단한 예제로는 예외처리가 뭐가 더 낫다는건지 감이 잘 오지 않습니다. 저도 그랬으니까요. 자~ 코드가 복잡해집니다. 다양한 기능이 붙고, 추상화니, 메시지 핸들러니 각종 쌈박한 로직을 구현해두었습니다. 만들다보니 기능이 붙고 붙네요. 외부 라이브러리도 붙었습니다. 정규식은 boost쓰고, 컨테이너는 stl 쓰고, json은 json spirit, xml은 tinyxml 을 붙였군요. 제가 이 글을 쓰고자 했던건 사실 tinyxml 때문이었습니다. tinyxml 물론 잘 만들어진 라이브러리입니다. 저도 아주 잘 쓰고 있고요. 헌데 이 tinyxml에서 문제가 하나 있었습니다.
TiXmlElement* pNickname = pPlayerElem->FirstChildElement("nickname"); if (pNickname == NULL) return false; std::string nickname = pNickname >GetText();문제가 된 부분이 보이시나요? 전 몇번이나 코드를 훓어보던 중에야 원인을 찾을 수 있었습니다.
원인은 pNickname >GetText() 이 함수가 문제였습니다.
위 코드는 nickname을 문자열형으로 읽어내는 코드입니다. 위 코드는 정상적으로 데이터가 포함되어있을 땐(<Nickname>엘키</Nickname>) 문제가 없습니다. 헌데 만약
비어있을 때 문제가 생긴다는 것도, 코드를 자세히 들여다 보고 나서야 확인한 것이지, 덤프를 본 직후에 바로 알아볼 순 없었어요.
한 줄 한 줄 코드를 검토하며 함수 내부를 들여다 본 순간… 아차 싶었습니다.
const char* TiXmlElement::GetText() const { const TiXmlNode* child = this->FirstChild(); if ( child ) { const TiXmlText* childText = child->ToText(); if ( childText ) { return childText->Value(); } } return 0; }child->ToText() 함수가는 내부에 포함된 데이터가 비어있을 때 NULL이 반환되고, 이어 함수 최하단에서 return 0; 코드를 타 NULL 포인터가 반환되었습니다.
2. const char*형은 문자열형만 반환될뿐 사실은 pointer의 개념보다는 문자열 첫 주소가 반환된다는 의미로도 인식되어, NULL 포인터가 반환될 거라 인식하기 어려운 문제를 가지고 있는것이죠.
TiXmlElement* pNickname = pPlayerElem->FirstChildElement("nickname"); if (pNickname == NULL) return false; if(pNickname->GetText() == NULL) return false; std::string nickname = pNickname >GetText(); TiXmlElement* pUserID = pPlayerElem->FirstChildElement("user_id"); if (pUserID == NULL) return false; if(pUserID >GetText() == NULL) return false; std::string user_id= pUserID>GetText();이렇게 해주어야합니다. GetText를 사용하는 코드가 많으면 많을수록 if문은 늘어납니다. 물론 위의 if문을
if (pUserID == NULL || pUserID >GetText() == NULL) return false;or 연산으로 수정할수야 있겠지만, 그렇다손쳐도 GetText() 하는 함수마다 체크해주어야 함은 변함이 없습니다.
const char* TiXmlElement::GetText() const { const TiXmlNode* child = this->FirstChild(); if ( child == NULL) { throw std::exception("TiXmlElement::GetText() FirstChild is NULL"); } const TiXmlText* childText = child->ToText(); if ( childText == NULL) { throw std::exception("TiXmlElement::GetText() ToText Failed."); } return childText->Value(); }이렇게 코드를 작성했다면 어떨까요?
try { TiXmlElement* pNickname = pPlayerElem->FirstChildElement("nickname"); if (pNickname == NULL) continue; std::string nickname = pNickname >GetText(); TiXmlElement* pUserID = pPlayerElem->FirstChildElement("user_id"); if (pUserID == NULL) return false; std::string user_id= pUserID>GetText(); } catch(const std::exception& e) { std::cout << e.what() << std::endl; // 일괄적인 예외 핸들링 return false; }
만약 try안에서 호출된 다른 함수에서 throw가 존재한다해도 코드의 흐름을 멈추고, 원인을 알아낼 수 있게 됩니다.
이렇게 되면 예외처리를 일괄적으로 할 수 있고, 코드의 흐름을 중단하는 역할도 맡길 수가 있습니다.
보통 외부 라이브러리 함수에서 크리티컬한 상황이 발생했을 경우라거나, 유틸리티 함수등에서의 실패를 리턴값으로 반환하곤 하는데, 이보다 더 강하게 코드의 진행을 중지 시키고 싶을 때 (2차 감염을 막고, 현재 상황의 위험성을 알리기 위해서 주로 이렇게 하죠) 라고 볼 수 있습니다.
리턴값은 그 함수를 사용하는 코드마다 체크 로직과 핸들링 로직을 구현해주어야 합니다.
이에 비해 예외 처리는, 적절한 위치에 try-catch로 묶어주기만해도 throw로 던져진 예외를 핸들링 할수가 있게 됩니다.
물론 모든 코드가 그렇게 예외처리를 하는 것은, 코드의 흐름을 원치 않는 곳에서 중단 시키기 때문에, 상황에 따라 국소적인 코드마다 try-catch 핸들링을 해주어야 하는 부작용도 있긴하지만, 순작용이 훨씬 많은 예외 처리 방식입니다.
심지어 이는 C++에 국한된 것이 아닌, 대다수의 언어에서 지원되는 예외처리 방식이므로 한번 몸에 익혀두시면 두고 두고 활용하시기에 좋습니다.
C++도 표준 라이브러리들은 대부분 throw로 예외처리를 하기 때문에 try-catch로 예외처리를 할수있고, ruby등의 기타 스크립트 언어들도 내부 함수 예외를 try-catch로 핸들링 할 수 있습니다.
try-catch 적용시 많이들 어렵게 생각하시는 점은, 어디서 부터 어디까지 try-catch로 감싸도 되는가에 대한 고민이라고 생각하는데요, 이는 boost나 stl등 내부적으로 throw를 사용하고 있는 함수들 부터 적용해보시면 어렵지 않게 적응하실 수 있습니다.
코드의 흐름을 제어하고, 일반화된 오류 처리를 도와주는 예외처리. 적극적으로 써보시면 어떨까요?
'C++ > General' 카테고리의 다른 글
Code::blocks - cross platform 개발을 위한 C++ IDE (0) | 2014.12.30 |
---|---|
Routine Mappings (TCHAR, char, WCHAR 호환 메소드) (0) | 2013.10.16 |
구조체 이니셜라이저 문제 (0) | 2013.06.14 |
C++ 11 주요 Feature 정리 (C++ 0x에서 추가됐던 내용 포함) (0) | 2013.04.05 |
Smart Pointers (Modern C++) (0) | 2013.02.06 |
- Total
- Today
- Yesterday
- 임백준
- 좋은 프로그래머
- 루비
- 루비 온 레일즈
- MS-SQL
- perfmon
- RoR
- NDC2013
- SQLite Spy
- Rails
- EzShortcut
- svn
- 조엘 온 소프트웨어
- ftp
- 리버스 엔지니어링
- 게임개발포에버
- CppSQLite
- ruby
- 멀티스레드
- 바로가기 프로그램
- 게임데브포에버
- TraceRoute
- 디버깅
- SDL
- 디자인 패턴
- EasyExec
- 엘키
- TDD
- Ruby on Rails
- c언어
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |