일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 더 지니어스 양면포커
- pointer to member data
- vector capacity
- operator overloading
- conversion constructor
- c++ basic practice
- virtual function table
- virtual function
- vector size
- std::vector
- delete function
- this call
- new&delete
- discord bot
- virtual destructor
- increment operator
- base from member
- std::endl
- member function pointer
- suffix return type
- C++
- virtual inheritance
- diamond inheritance
- placement new
- return by reference
- c++ multi chatting room
- constructor
- dynamic_cast
- std::cout
- std::ostream
- Today
- Total
I'm FanJae.
[C++] Exception 본문
1. Error Handling
void db_backup()
{
// 서버에 접속해서 DB를 백업하는
// 기능 수행하다가 오류가 발생했다면?
}
void db_clear()
{
}
int main()
{
db_backup();
db_clear();
}
- 함수에서 특정 기능을 수행하는 도중 오류가 발생하여, 처리해야 하는 경우 어떻게 할 것인가?
① 함수 내에서 프로그램(프로세스)를 종료
- std::exit() 등의 함수 사용
- 보통, 심각한 경우 사용한다.
② 호출자에게 오류가 발생했음을 알린다.
- 일반적인 상황에서 많이 사용한다.
1-2. 호출자에서 함수가 실패 했음을 알리는 방식
① 약속된 함수 반환 값(-1 등)으로 실패 전달.
- C 언어 및 대부분의 언어에서 사용
② 예외(Exception) 사용
- 대부분의 객체지향 언어의 대표적인 방법(C++, java, C#, Python 등)
③ 성공시 결과값 + 실패를 모두 담을 수 있는 타입을 반환하는 방식
- 함수형 언어 및 일부 최신 언어가 사용
※ 1개만 사용하는 것이 아니라 오류의 심각성에 따라 위 방식을 섞어서 사용한다.
1-1-1. 함수 반환 값으로 오류를 전달하는 방식
#define ERROR -1
void db_backup()
{
// 서버에 접속해서 DB를 백업하는
// 기능 수행하다가 오류가 발생했다면?
if ( 실패 )
return ERROR;
return 0;
}
void db_clear()
{
}
int main()
{
db_backup();
db_clear();
}
- 가장 널리 사용되던 방식(C언어)
- 오류를 나타내는 값을 약속 한다. (ex : -1)
- 오류 발생시 약속된 값을 반환한다.
- 함수 반환 타입이 void 라는 것은 함수가 절대 실패하지 않는다는 것을 의미한다.
※ 이 방식의 단점
① 반환 값과 오류가 완벽히 분리되지 않는다.
- -1이 실패를 의미 하는지 연산의 결과인지 파악이 모호하다.
② 호출자가 발생된 오류를 무시할 수 있다.
- 이것이 상당히 심각한 문제이다.
int main()
{
db_backup(); // 오류 처리를 하지 않았다.
db_clear();
}
- 지금 main()에서는 오류를 처리 하지 않았다.
- 즉, DB 백업이 실패했는데 호출자가 무시하고 DB를 지워버렸다면, 어떻게 되겠는가?
※ 즉, 심각한 오류는 호출자가 반드시 처리를 강제해야 한다.
2. Exception
#include <iostream>
int db_backup()
{
if ( 실패 )
return -1;
std::cout << "continue db_backup" << std::endl;
return 0;
}
int main()
{
int ret = db_backup();
std::cout << "continue main" << std::endl;
}
- 예외(Exception) 이란 객체지향 언어에서 많이 사용하는 오류 처리 방식이다.
2-1. 예외(Exception) 문법을 사용하는 방법
① 함수 실패시 trhow 키워드를 사용해서 예외를 전달한다. 보통은 던진다(throw)라고 표현하기도 한다.
② throw를 하면 함수는 더 이상 실행되지 않고 호출자의 catch 문으로 이동한다.
#include <iostream>
int db_backup()
{
if ( 실패 )
throw -1;
std::cout << "continue db_backup" << std::endl;
return 0;
}
int main()
{
int ret = db_backup();
std::cout << "continue main" << std::endl;
}
③ 호출자는 try, catch 키워드를 사용해서 던져진 예외를 잡는다.
④ 호출자가 던져진 예외를 처리하지 않으면 프로그램은 종료된다. -> abort() 함수가 호출된다.
실패라는 문구를 1로 바꿔서 visual studio에서 실행하면 아래와 같은 창이 뜬다.
※ Exception의 특징
- 함수 반환 값과 오류의 전달이 완벽히 분리된다.
- 호출자는 반드시 오류를 처리해야만 한다.
2-2. 던져진 예외를 처리하는 방법
try
{
// 예외 발생 가능성이 있는 함수를 호출 할 때는
// try { } 안에서 호출한다. 예외 발생시 catch로 이동한다.
int ret = db_backup();
// 예외 발생이 해당 문구 아래쪽은 실행되지 않을 것이다.
}
catch ( int e )
{
// 예외가 발생된 경우만 실행된다.
}
- 던져진 예외를 처리하는 가장 기본적인 형태는 이와 같다.
- 예외 발생 가능성이 있는 함수를 호출 할때는 try { }안에서 호출 한다. 예외 발생시 catch로 이동한다.
- 즉, int ret = db_backup(); 에서 예외가 발생했다면 그 아래 문구는 실행이 되지 않는 것이다.
2-2-1. 적용
#include <iostream>
int db_backup()
{
if ( 1 )
throw 1;
std::cout << "continue db_backup" << std::endl;
return 0;
}
int main()
{
try
{
// 0
int ret = db_backup();
// 1
std::cout << "DB backup complete." << std::endl;
}
catch(int e)
{
// 2
std::cout << "exception!!" << std::endl;
}
// 3
std::cout << "continue main" << std::endl;
}
- 코드의 주석 상 0번 부분부터 차례대로 실행된다. 예외 발생 가능성 있는 함수 호출시 try { } 안에서 호출될 것이다.
- db_backup()은 오류 발생시 throw를 하고 있다. 예외 발생시 1번 이후 문장은 실행(DB backup complete)되지 않는다.
- 예외 발생시 2번으로 이동하여, exception이 출력된다.
- 예외 여부 상관 없이 3번은 정상적으로 실행된다.
※ 즉, try 블록 안에서 예외 가능성이 있는 함수를 호출하고, 발생되는 예외는 catch로 처리하면 된다.
2-3. 오류에 대한 정보 전달
#include <iostream>
int db_backup()
{
if ( 1 )
throw 1;
std::cout << "continue db_backup" << std::endl;
return 0;
}
int main()
{
try
{
int ret = db_backup();
std::cout << "DB backup complete." << std::endl;
}
catch(int e)
{
std::cout << "exception!!" << std::endl;
}
std::cout << "continue main" << std::endl;
}
- 오류 발생시, 오류에 대한 자세한 정보를 호출자에게 전달하는 것이 좋다.
- DB 연결 실패 사유는 다양하다. (파일 정보 없음, 네트워크 연결, DB 인증 등)
- 즉, 오류에 대한 자세한 정보를 담은 타입을 설계해서 객체를 반환한다.
- 아래는 그 예시이다. (구체적 구현은 생략했다.)
class file_not_found
{
// 오류의 정보를 담는 멤버.
};
#include <iostream>
class file_not_found {};
int db_backup()
{
if ( 1 )
throw file_not_found();
std::cout << "continue db_backup" << std::endl;
return 0;
}
int main()
{
try
{
int ret = db_backup();
std::cout << "DB backup complete." << std::endl;
}
catch(const file_not_found& e)
{
std::cout << "file_not_found!!!" << std::endl;
}
std::cout << "continue main" << std::endl;
}
- 이와 같이 객체를 반환하는 것이 일반적이다.
- 자세한 정보가 없다고 하더라도 클래스 이름만을 보더라도 가독성이 좋아진다.
- int를 받았을때 보다 어떤 문제가 있는지 알아보기 좋다. (file_not_found라는 것을 보고 파일이 없음을 확신할 수 있다.)
- C++ 표준도 일부 함수(멤버 함수)가 실패하면 예외를 던진다.
※ 즉, C++ 표준의 예외 전용 클래스가 있다. cppreference.com 사이트에서 이를 확인할 수 있다.
2-4. 여러 오류의 처리
- 함수는 여러 가지의 사유로 실패할 수 있다.
- 이에 따라서 catch도 여러개를 만들 수 있다.
#include <iostream>
class file_not_found {};
class network_disconnected {};
class authentication_failed {};
int db_backup()
{
if ( 1 )
throw file_not_found();
std::cout << "continue db_backup" << std::endl;
return 0;
}
int main()
{
try
{
// 0
int ret = db_backup();
// 1
std::cout << "DB backup complete." << std::endl;
}
catch(const file_not_found& e)
{
// 2
std::cout << "file_not_found!!!" << std::endl;
}
// 3
std::cout << "continue main" << std::endl;
}
- 현재 본 예제에서는 file_not_found에 대한 catch만이 존재한다.
- 이 경우 다른 예외가 발생한 경우 프로그램은 비정상 종료. 즉, abort()를 호출한다.
catch(const file_not_found& e)
{
// 2
std::cout << "file_not_found!!!" << std::endl;
}
catch(const network_disconnected& e)
{
// 2
std::cout << "network_disconnected" << std::endl;
}
- 이런 방식으로 사용할 수 있다.
2-4-1. 예외가 많아서 모두 적기 어려운 경우 // catch(...)
catch(const file_not_found& e)
{
std::cout << "file_not_found!!!" << std::endl;
}
catch(const network_disconnected& e)
{
std::cout << "network_disconnected" << std::endl;
}
catch(...) // 다른 모든 예외에 대한 처리
{
std::cout << "..." << std::endl;
}
catch(...)
{
}
- 이 문장은 가급적 다른 모든 예외의 마지막에 처리되도록 권장하고 있다.
- 버전에 따라서 이것이 다른 예외보다 앞에 있을때 오류를 발생 시키는 경우도 있고, 그렇지 않은 경우도 있다.
- 따라서, 반드시 마지막에 놓아야 한다.
3. Standard Exception
3-1. 예외와 성능
- 예외처리를 위해 생성되는 기계어 코드가 오버헤드가 존재한다.
- 성능이 중요한 분야에서는 예외를 사용하지 않는 경우도 있다.
- C#, Java, Python 등의 언어에서는 예외를 많이 사용한다.
- C++의 일부 함수가 예외를 발생 시킨다.
3-1-1. std::stoi()
- <string> 헤더
- 인자로 전달된 문자열을 정수(int)로 변경해서 반환하는 함수이다.
#include <iostream>
#include <string>
int main()
{
int n = std::stoi("10");
std::cout << "result : ", n;
}
- 이는 문제없이 실행된다.
#include <iostream>
#include <string>
int main()
{
int n = std::stoi("FanJae");
std::cout << "result : " << n;
}
- 이 문장은 abort()를 호출한다고 실행이 나오지만, Debug를 해보면 실제로 어떤 Exception을 반환하는지 알 수 있다.
- 실행했을때는 알 수 없지만 std::invalid_argument라는 타입의 예외를 발생 시킨다.
#include <iostream>
#include <string>
int main()
{
try
{
int n = std::stoi("FanJae");
std::cout << "result : " << n;
}
catch ( const std::invalid_argument& e)
{
std::cout << e.what() << std::endl;
}
}
- std::invalid_arguement에 멤버 함수에는 what이라는 것이 존재한다.
- 이를 실행해보면, 다음과 같은 정보를 제공해준다.
3-2. 어떤 예외를 발생시키는지 판단이 어려운 경우
① catch(...)을 사용하는 방법
#include <iostream>
#include <string>
int main()
{
try
{
int n = std::stoi("FanJae");
std::cout << "result : " << n;
}
catch(...)
{
std::cout << "Exception" << std::endl;
}
}
② std::exception을 사용하는 방법
#include <iostream>
#include <string>
int main()
{
try
{
int n = std::stoi("FanJae");
std::cout << "result : " << n;
}
catch(const std::exception& e)
{
std::cout << e.what << std::endl;
}
}
- 모든 C++ 예외 클래스는 std::exception 이라는 클래스로 부터 파생된다.
- 모든 예외 타입에는 what이라는 가상함수가 존재한다.
3-3. std::vector의 예외
#include <iostream>
#include <vector>
int main()
{
std::vector v = {1, 2};
v[100] = 0; // 잘못된 인덱스인 경우 미정의 동작
v.at(100) = 0; // 잘못된 인덱스인 경우 Exception
std::cout << "continue main" << std::endl;
}
- std::vector의 요소 접근
[] | 잘못된 인덱스 전달시 undefine behavior |
at() | 잘못된 인덱스 전달시 std::out_of_range 타입의 예외 발생 |
#include <iostream>
#include <vector>
int main()
{
std::vector v = {1, 2};
try
{
v.at(100) = 0; // 잘못된 인덱스인 경우 Exception
}
catch( const std::out_of_range& e)
{
std::cout << "what : " << e.what() << std::endl;
}
std::cout << "continue main" << std::endl;
}
- 이처럼, C++ 표준을 확인해보면 어떤 경우는 예외가 있으나 어떤 경우는 예외가 없는 것을 확인할 수 있다.
- C++ Reference에서 이런 함수를 찾아보면, 설명에 예외가 나오는지 여부를 확인할 수 있다.
'C++ > Basic' 카테고리의 다른 글
[C++] stack unwinding (0) | 2024.08.29 |
---|---|
[C++] Algorithm (3) | 2024.08.28 |
[C++] Iterator (0) | 2024.08.28 |
[C++] STL Container II (0) | 2024.08.27 |
[C++] STL Container I (0) | 2024.08.27 |