예외란?
예외란 문법적인 오류가 아닌 프로그램이 의도하지 않은 오류를 말합니다. 즉 개발자가 예상하는 동작을 제외한 나머지 전체라고 할 수 있습니다. 대부분의 경우 예외가 발생하면 시도했던 모든일들을 취소하고 그 전 상태로 되돌아가도록 프로그램을 작성합니다. 좀 더 심각한 경우라면 프로그램을 종료해야겠죠.
예외와 오류는 명백히 다릅니다. 오류가 났을때 프로그램이 터지는 현상을 막으려고 억지로 예외처리를 하여 동작시키려 하는것은 잘못된 행위입니다. 오류가 나지않도록 프로그래밍을 하는것이 가장 중요하고 오류가 난다면 그냥 프로그램이 터지도록 내버려두는 편이 낫습니다.
try catch문 사용법
C언어에서는 예외처리를 조건문 if문으로 처리를 해왔습니다. if문으로 예외처리를 하는것도 잘못된것은 아니지만 좀 더 이것이 예외처리를 위한 코드다 라는 명시적인 목적과 예외처리를 좀 더 쉽게 해줄 목적으로 C++에서는 예외 상황을 다룰 수 있는 구조를 문법으로 제공합니다. 이것이 바로 try-catch문입니다.
문법
try { // 예외가 발생하는 영역
if (예외 조건) throw 예외 객체; // 예외가 발생하면 예외를 던지는 영역
}
catch (예외 객체) { // 던져진 예외를 잡는 영역
// 예외 처리 영역
}
try : 블록 내부에 예외가 있는지 탐색합니다.
throw : try에서 예외를 탐색하면 throw에서 catch로 인자를 던집니다.
catch : throw에서 던진 변수를 인자로 받아서 catch 블록 내부에 정의한 예외처리를 합니다.
예외 상황
#include <iostream>
#include <vector>
using namespace std;
void main() {
vector <int> v{1,2,3};
while(true){
v.pop_back();
}
}
가장 대표적인 예외가 리스트나 배열의 오버플로우, 언더플로우 상황일것입니다. 위의 예제를 보시면 vector에는 1,2,3 이렇게 3개의 원소밖에 없는데 pop을 무한 반복하여 결국에는 원소가 0인데도 pop을 하는 언더플로우가 발생하여 프로그램이 터지게 됩니다.
예외 추가
#include <iostream>
#include <vector>
using namespace std;
void main() {
vector <int> v{1,2,3};
try
{
while (true) {
if (v.size() <= 0) { //예외 발생
throw string("Underflow!");
}
v.pop_back();
}
}
catch (string error)
{
cout << error << endl;
}
}
위와 같이 vector의 크기가 0일때를 예외로 줘서 프로그램 오작동을 방지할 수 있습니다. 예외가 발생하는 경우를 try에 넣고 예외가 발생하면 throw를 해줘 catch문으로 넘겨줍니다. try catch를 할거면 throw를 받아야합니다. 그렇지 않다면 try catch를 하는 의미가 없습니다. 참고로 throw에서 던져진 예외 데이터타입과 이 예외를 받는 catch구문의 매개변수의 데이터 타입이 서로 일치해야 합니다. 그렇지 않다면 예외처리는 발생하지 않고 terminate 함수가 호출되어 그대로 프로그램이 종료됩니다.
다중 예외 처리
#include <iostream>
#include <vector>
using namespace std;
void main() {
vector <int> v{1,2,3};
try
{
for (int i = 0; i < 5; i++) {
if (v.size() <= 0) {
throw string("Underflow!");
}
if (v.size() < i) {
throw i;
}
cout << v[i] << endl;
v.pop_back();
}
}
catch (string error)
{
cout << error << endl;
}
catch (int index)
{
cout << "v["<< index << "]는 vector의 범위를 벗어났습니다." << endl;
}
}
위와 같이 catch문을 여러개 정의하여 다중 예외 처리도 가능합니다.
함수에서의 예외처리
#include <iostream>
#include <vector>
using namespace std;
void pop(vector<int> &v) {
if (v.size() <= 0) { //예외 발생
throw string("Underflow!");
}
v.pop_back();
}
void main() {
vector <int> v{ 1,2,3 };
try
{
while (true) {
pop(v);
}
}
catch (string error)
{
cout << error << endl;
}
}
throw를 해주는 부분과 catch를 해주는 부분이 달라도 상관이 전혀 없습니다. 위의 예제를 보시면 함수 내부에서 throw를 하고 있습니다. 이렇게 될 경우 함수 내에는 예외를 처리하는 영역이 없기 때문에 해당 함수가 호출된 영역으로 예외를 전달합니다. 위의 예제의 경우에는 main이 되겠죠. main의 try안에 있는 pop함수에서 예외가 발생하였기 때문에 catch영역에서 예외를 처리하게 됩니다. 이와 같은 형식을 Stack풀기라고 합니다.
Stack풀기
#include <iostream>
using namespace std;
void func1() { throw string("예외 발생"); }
void func2() { func1(); }
void func3() { func2(); }
int main()
{
try {
func3();
}
catch (string exception) {
cout << exception << endl;
}
return 0;
}
함수에서의 예외처리와 마찬가지로 예외상황이 발생한 위치와 예외상황을 처리해야하는 위치가 다른 경우에는 throw로 예외를 던지면서 던진 영역이 try구문이 아니기 때문에 함수는 종료되고 예외처리에 대한 책임은 함수를 호출한 부분으로 넘어갑니다. 넘어간 영역이 try구문일때까지 계속해서 반복되며 try구문에 도착하였을때 이에 해당하는 catch영역에서 예외처리를 하게됩니다. 실제로 코딩을 하다보면 이러한 구조가 훨씬 많습니다.
마치며
오늘 작성한 예외처리 try-catch는 깔끔하긴 하지만 사용하면 할수록 프로그램 성능에는 악영향을 미쳐서 무작정 사용하면 안됩니다. 위의 예제와 같은 경우는 try-catch문의 사용법을 위해 작성한 예제이지만 사실 충분히 예측이 가능한 예외이기때문에 예외를 해주기보다는 저렇게 사용하지 않는것을 중점으로 둬야겠죠. 일반적으로 예외처리를 해야하는 경우라면 일반적으로 서버같은 경우에서 입력값에 오염이 발생하는등의 예측할 수 없는 경우가 생겨 큰 문제가 일어나면 안되기때문에 특정 예외가 발생하여도 서버가 강제로 종료되지 않고 버티도록 하는 경우에 종종 사용됩니다.
'Language > C , C++ , C#' 카테고리의 다른 글
[C언어] C언어로 만든 간단한 슈팅게임 (+ 소스 코드) (4) | 2022.02.03 |
---|---|
[C++] string 클래스 (문자열 다루기) 사용법 & 예제 총정리 (0) | 2021.02.27 |
[C++] 연산자 오버로딩에 대하여 (0) | 2021.02.26 |
[C++] 복사 생성자, 복사 대입 연산자 (+깊은 복사) (0) | 2021.02.25 |