따라하며 배우는 C++ 14. 예외 처리
14.1 예외처리Exception Handling의 기본
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int findFirstChar(const char* string, char ch)
{
for (size_t index = 0; index < strlen(string); ++index)
{
if (string[index] == ch)
return index;
}
return -1;
}
double divide(int x, int y, bool& success)
{
if (y == 0)
{
success = false;
return 0.0;
}
success = false;
return static_cast<double>(x) / y;
}
int main()
{
bool success;
double result = divide(5, 3, success);
if (!success)
cerr << "An error occurred" << endl;
else
cout << "Result is " << result << endl;
ifstream input_file("temp.txt");
if(!input_file)
cerr << "Cannot open file" << endl;
return 0;
}
위의 문법대로 예전에는 많이 했다(C style)
예외처리가 문법적으로 좋아 보이지만, 정말로 예측할 수 없는 일이 빈번하게 발생하는 경우 많이 쓰인다.
예외 처리의 속도가 일반적인 오류 처리 방법보다 느리기 때문이다.
#include <iostream>
#include <string>
using namespace std;
int main()
{
//try, catch, throw
//try를 해서 아무 일도 없다면 계속 작동
//만약 이상한 일이 생기면 throw로 예외를 던진다
//catch는 던저진 에러를 잡아서 처리한다.
double x;
cin >> x;
try
{
if (x < 0.0) throw string("Negative input");
cout << sqrt(x) << endl;
}
catch (string error_message)
{
cout << error_message << endl;
}
return 0;
}
//예외 처리는 엄격하다. 형 변환 같은 경우에서도 그렇다.
위의 경우 error_message가 string으로 들어가기 때문에, 그냥 const char*로 하면 받지를 못한다.
try
{
if (x < 0.0) throw string("Negative input");
cout << sqrt(x) << endl;
}
catch (string error_message)
{
cout << error_message << endl;
}
catch (int x)
{
cout << "Catch Integer " << x << endl;
}
catch (const char* error_message)
{
cout << "Char * " << error_message << endl;
}
throw하는 타입에 맞춰 catch 처리를 할 수 있다.
type이 맞지 않을 경우 OS가 런타임 에러를 경고한다.
14.2 예외처리와 스택 되감기Satck Unwinding
#include <iostream>
#include <string>
using namespace std;
void last()
{
cout << "last " << endl;
cout << "Throws exception" << endl;
throw - 1;
cout << "End last " << endl;
}
void third()
{
cout << "Third" << endl;
last();
cout << "End third " << endl;
}
void second()
{
cout << "Second" << endl;
try
{
third();
}
catch (double)
{
cerr << "Second caught double exception" << endl;
}
cout << "End second " << endl;
}
void first()
{
cout << "first" << endl;
try
{
second();
}
catch (int)
{
cerr << "first caught int exception" << endl;
}
cout << "End first " << endl;
}
int main()
{
cout << "Start" << endl;
try
{
first();
}
catch (int)
{
//cerr는 에러 메시지, 버퍼를 거치지 않고 빠르게 출력.
//clog
cerr << "main caught int exception" << endl;
}
cout << "End Main" << endl;
return 0;
}
cout << "Start" << endl;
try
{
first();
}
catch (int)
{
//cerr는 에러 메시지, 버퍼를 거치지 않고 빠르게 출력.
//clog
cerr << "main caught int exception" << endl;
}
catch (...) //catch-all handlers
{
cerr << "main caught ellipses exception" << endl;
}
나머지 반환형의 catch에 대해선 ellipses를 사용할 수 있다.
void last() throw(int)
라고 함수를 선언할 경우 int형 에러를 throw할 수 있다는 말이다.
(필요가 없다는 견해가 더 많다)
void last() throw() 이렇게 걸면, 함수가 아무것도 예외를 던지지 않는다는 뜻
void last() throw(...) 이렇게 걸면, 어떠한 반환형의 throw이든지 던지겠다는 뜻
14.3 예외 클래스와 상속
#include <iostream>
#include <string>
using namespace std;
class MyArray
{
private:
int m_data[5];
public:
int& operator[] (const int& index)
{
if (index < 0 || index >= 5) throw - 1;
return m_data[index];
}
};
void doSomething()
{
MyArray my_array;
try
{
my_array[100];
}
catch (const int& x)
{
cerr << "Exception " << x << endl;
}
}
int main()
{
doSomething();
return 0;
}
#include <iostream>
#include <string>
using namespace std;
class Exception
{
public:
void report()
{
cerr << "Exception report" << endl;
}
};
class MyArray
{
private:
int m_data[5];
public:
int& operator[] (const int& index)
{
if (index < 0 || index >= 5) throw Exception();
return m_data[index];
}
};
void doSomething()
{
MyArray my_array;
try
{
my_array[100];
}
catch (const int& x)
{
cerr << "Exception " << x << endl;
}
catch (Exception& e)
{
e.report();
}
}
int main()
{
doSomething();
return 0;
}
예외 클래스를 새로 정의할 수 있다.
#include <iostream>
#include <string>
using namespace std;
class Exception
{
public:
void report()
{
cerr << "Exception report" << endl;
}
};
class ArrayException : public Exception
{
public:
void report() //overriding
{
cerr << "Array exception" << endl;
}
};
class MyArray
{
private:
int m_data[5];
public:
int& operator[] (const int& index)
{
if (index < 0 || index >= 5) throw ArrayException();
return m_data[index];
}
};
void doSomething()
{
MyArray my_array;
try
{
my_array[100];
}
catch (const int& x)
{
cerr << "Exception " << x << endl;
}
catch (Exception& e)
{
e.report();
}
catch (ArrayException& e)
{
e.report();
}
}
int main()
{
doSomething();
return 0;
}
위의 경우, doSomething()에서 ArrayException을 던지고 있지만
catch에서 Exception(부모 클래스)가 먼저 있으므로 ArrayException은 Exception에 걸리고 만다.
이를 해결하기 위해선 catch(Exception & e)보다 catch(ArrayException & e)를 먼저 쓰기만 하면 된다.
#include <iostream>
#include <string>
using namespace std;
class Exception
{
public:
void report()
{
cerr << "Exception report" << endl;
}
};
class ArrayException : public Exception
{
public:
void report() //overriding
{
cerr << "Array exception" << endl;
}
};
class MyArray
{
private:
int m_data[5];
public:
int& operator[] (const int& index)
{
if (index < 0 || index >= 5) throw ArrayException();
return m_data[index];
}
};
void doSomething()
{
MyArray my_array;
try
{
my_array[100];
}
catch (const int& x)
{
cerr << "Exception " << x << endl;
}
catch (ArrayException& e)
{
e.report();
}
catch (Exception& e)
{
e.report();
}
}
int main()
{
try
{
doSomething();
}
catch (ArrayException& e)
{
cout << "main()" << endl;
e.report();
}
return 0;
}
이 경우 main에서는 단연 에러가 잡히지 않는다.
그러나 다시 에러를 던져야 하는 경우, re-throw를 할 수 있다.
#include <iostream>
#include <string>
using namespace std;
class Exception
{
public:
void report()
{
cerr << "Exception report" << endl;
}
};
class ArrayException : public Exception
{
public:
void report() //overriding
{
cerr << "Array exception" << endl;
}
};
class MyArray
{
private:
int m_data[5];
public:
int& operator[] (const int& index)
{
if (index < 0 || index >= 5) throw ArrayException();
return m_data[index];
}
};
void doSomething()
{
MyArray my_array;
try
{
my_array[100];
}
catch (const int& x)
{
cerr << "Exception " << x << endl;
}
catch (ArrayException& e)
{
cout << "doSomething()" << endl;
e.report();
throw e;
}
catch (Exception& e)
{
e.report();
}
}
int main()
{
try
{
doSomething();
}
catch (ArrayException& e)
{
cout << "main()" << endl;
e.report();
}
return 0;
}
함수에 대해 rewinding이 되므로, something 함수의 밑 쪽 catch가 아닌,
something 함수를 부른 main에서 에러를 다시 catch할 수 있다.
#include <iostream>
#include <string>
using namespace std;
class Exception
{
public:
void report()
{
cerr << "Exception report" << endl;
}
};
class ArrayException : public Exception
{
public:
void report() //overriding
{
cerr << "Array exception" << endl;
}
};
class MyArray
{
private:
int m_data[5];
public:
int& operator[] (const int& index)
{
if (index < 0 || index >= 5) throw ArrayException();
return m_data[index];
}
};
void doSomething()
{
MyArray my_array;
try
{
my_array[100];
}
catch (const int& x)
{
cerr << "Exception " << x << endl;
}
catch (Exception& e)
{
cout << "in doSomething()" << endl;
e.report();
throw e;
}
}
int main()
{
try
{
doSomething();
}
catch (Exception& e)
{
cout << "main()" << endl;
e.report();
}
catch (ArrayException& e)
{
cout << "main()" << endl;
e.report();
}
return 0;
}
이렇게 exception을 거친 후 다시 throw e를 하면 main에 와서도 원래형 "ArrayException"이 아닌 그냥 "Exception"으로 받는다.
이 때 throw e;가 아닌 throw; 만 하면 외부에서는 원래의 에러형 " ArrayException"으로 받을 수 있게 된다!
14.4 std::exception 소개
#include <iostream>
#include <string>
#include <exception>
using namespace std;
int main()
{
try
{
string s;
s.resize(-1);
}
catch (exception& exception)
{
cerr << exception.what() << endl;
//what() 무슨 에러인지.
}
return 0;
}
exception 클래스는 자식을 많이 가지고 있다.
typeid(exception).name()
처럼 사용하면, 어떤 에러가 생겼는지 그 자식 클래스의 이름을 자세히 알 수 있다.
https://en.cppreference.com/w/
위 페이지에서 메소드를 찾고, 해당 메소드에서 어떤 예외가 던져질 수 있는지 확인할 수도 있다.
#include <iostream>
#include <string>
#include <exception>
using namespace std;
int main()
{
try
{
/*string s;
s.resize(-1);*/
throw runtime_error("Bad thing happened");
}
catch (exception& exception)
{
cout << typeid(exception).name() << endl;
cerr << exception.what() << endl;
//what() 무슨 에러인지.
}
return 0;
}
직접 except 클래스의 자식 클래스들 중 하나를 에러로 던질 수 있다.
#include <iostream>
#include <string>
#include <exception>
using namespace std;
class CustomException : public exception
{
public:
const char* what() const noexcept override
//noexcept : 적어도 이 안에선 exception을 던지지 않겠다.
{
return "Custom exception";
}
};
int main()
{
try
{
/*string s;
s.resize(-1);*/
throw CustomException();
}
catch (exception& exception)
{
cout << typeid(exception).name() << endl;
cerr << exception.what() << endl;
//what() 무슨 에러인지.
}
return 0;
}
exception 클래스를 상속받아 what()을 오버라이딩을 할 수 있다.
14.5 함수 try
#include <iostream>
#include <string>
#include <exception>
using namespace std;
void doSomething()
try
{
throw - 1;
}
catch (...)
{
cout << "Catch in doSomething()" << endl;
}
int main()
{
try
{
doSomething();
}
catch (...)
{
cout << "Catch in main()" << endl;
}
return 0;
}
함수에 바로 try-catch를 사용할 수 있지만 많이 사용되진 않는다.
#include <iostream>
#include <string>
#include <exception>
using namespace std;
class A
{
private:
int m_x;
public:
A(int x) : m_x(x)
{
if (x <= 0)
throw 1;
}
};
class B : public A
{
public:
B(int x)
: A(x)
{
}
};
int main()
{
try
{
B b(0);
}
catch (...)
{
cout << "Catch in main()" << endl;
}
return 0;
}
이렇게 하면 물론 메인에서 예외를 잡는다.
가끔 생성자에서 에러를 잡고 싶은 경우.
#include <iostream>
#include <string>
#include <exception>
using namespace std;
class A
{
private:
int m_x;
public:
A(int x) : m_x(x)
{
if (x <= 0)
throw 1;
}
};
class B : public A
{
public:
B(int x) try : A(x)
{
//do initialization
}
catch (...)
{
cout << "Catch in B constructor " << endl;
//throw;
}
};
int main()
{
try
{
B b(0);
}
catch (...)
{
cout << "Catch in main()" << endl;
}
return 0;
}
이 때 생성자에서 예외 처리를 하면, throw를 한 것처럼 메인에서도 한 번 더 잡는다.
(일반적인 함수에선 저렇게 진행되진 않는다.)
14.6 예외처리의 위험성과 단점
#include <iostream>
#include <string>
#include <exception>
using namespace std;
int main()
{
try
{
int* i = new int[10000];
//do something with i
throw "error";
delete[] i;
}
catch (...)
{
cout << "catch" << endl;
}
return 0;
}
위 코드처럼 사용할 경우, i에서 예외가 발생한다면, delete가 실행되지 않고 예외가 처리되어
메모리 누수가 발생할 수 있다.
#include <iostream>
#include <memory>
using namespace std;
int main()
{
try
{
int* i = new int[10000];
unique_ptr<int> up_i(i);
//do something with i
throw "error";
//delete[] i;
}
catch (...)
{
cout << "catch" << endl;
}
return 0;
}
이것을 해결할 수 있는 방법 중에 library <memory>에 들어 있는 unique_ptr이 유용하다.
delete가 없어도 알아서 지워주고, 예외가 발생해도 잘 처리해준다.
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
~A()
{
throw "error";
}
};
int main()
{
try
{
A a;
}
catch (...)
{
cout << "catch" << endl;
}
return 0;
}
Destructor에선 예외를 못 던지게 막고 있다.
'개발 공부 > C++' 카테고리의 다른 글
따라하며 배우는 C++ 16. 표준 템플릿 라이브러리 (0) | 2020.07.03 |
---|---|
따라하며 배우는 C++ 15. 의미론적 이동과 스마트 포인터 (0) | 2020.07.03 |
따라하며 배우는 C++ 13. 템플릿 (0) | 2020.07.02 |
따라하며 배우는 C++ 12. 가상 함수들 (0) | 2020.07.02 |
따라하며 배우는 C++ 11. 상속 (0) | 2020.07.02 |