1. 여러개의 예외는 처리가 곤란하다.
만약 이러한 클래스가 있고 벡터에서 10개의 Yeoul객체를 가지고 있다고 생각해보자.
class Yeoul
{
public:
~Yeoul(){}
};
void DoSomething()
{
std::vector<Yeoul>v;
}
벡터 v가 소멸될때에 벡터는 자신이 들고있던 Yeoul객체들을 소멸시켜야 할 책임을 가지고 있습니다.
만약에 첫번째 Yeoul객체를 소멸하던중 예외가 생겼다고 생각해봅시다.
나머지 9개의 객체도 소멸되어야함으로 벡터는 여전히 나머지 객체에 대해서 소멸자를 호출하게 됩니다.
그런데 두번째 Yeoul객체 역시 예외를 발생시키면 어떻게 될까요?
이렇게 예외가 2개이상이 되어버리면 C++에서 감당하기 어렵습니다.
이러한 동작의 원인은 바로 예외가 터지는 것을 내버려두는 소멸자에 있습니다.
결론은 C++는 예외를 내보내는 소멸자를 싫어합니다.
아직 첫번째 예외가 처리되지 않은 상태에서 또다른 예외가 발생하게 되면 C++는 std::terninate를 실행시킵니다.
-참고-
https://yeoul0714.tistory.com/34
[C++] 스택 풀기(Stack Unwinding)
스택 풀기란?스택 풀기는 예외가 발생한 후, 적절한 catch 블록을 찾아 제어를 이동시키는 과정입니다. 이 과정에서 예외 발생 지점부터 catch 블록까지의 스택 프레임들을 풀어내는 작업이 이루어
yeoul0714.tistory.com
2. 소멸자에서 예외가 생기는 코드
class DBConnection
{
public:
static DBConnection create();
void Close(); // 연결 실패시 예외 발생
};
이런 방식으로 코드가 설계되어 있으면 프로그래머가 직접 Close함수를 호출해주어야 합니다.
이러한 방법은 프로그래머가 깜빡하고 Close를 호출하지 않을수도 있다는 점입니다.
그럼 그냥 DBConnection객체를 관리하는 클래스의 소멸자에 Close를 넣어주면 어떨까요?
class DBConn // DBConnection 객체 관리 클래스
{
public:
~DBConn()
{
db.close();
}
private:
DBConnection db;
};
이렇게 설계하면 다음과 같이 사용 가능합니다.
DBConn dbc(DBConnection::create());
이러면 프로그래머가 Close함수를 호출하는 것을 까먹지 않을 수 있으니 정말 좋은 방법이죠?
물론 그것은 Close가 아무런 예외도 없이 정상적으로 실행되었을때의 이야기 입니다.
예외가 발생하면 이 예외를 처리하기 위해서 스택풀기 작업이 시작됩니다.
즉 예외가 소멸자에서 빠져나가게 됩니다.
이것은 결국 프로그래머에게 큰 걱정거리가 됩니다.
(소멸자에서 벗어나 또 다른 예외 만날시 terninate되어서 어디서 예외가 생긴지 알기 힘들어짐)
그럼 이러한 문제를 어떻게 해결해야 할까요?
3. 소멸자에서 예외가 생길때 해결법
3-1. 예외 생기면 바로 프로그램 종료
DBConn::~DBConn()
{
try {db.close();}
catch()
{
// close실패 로그
std::abort();
}
};
에러 발생후 프로그램이 더이상 실행이 불가한 상황이면 좋은 선택입니다.
괜히 예외를 흘려보내어서 예상치 못한 동작이 발생하는 것을 막는것이죠
abort를 써서 그런일이 일어나기전에 사전에 프로그램을 종료시켜버립니다.
3-2. 예외 먹방
DBConn::~DBConn()
{
try{db.close()}
catch()
{
//close호출 실패 로그
}
}
이렇게 예외를 먹어버리면 무엇 때문에 예외가 발생했는지 알 수 없습니다.
그러나 예외를 흘려보내어서 생기는 미정의 동작으로 인한 피해를 감수하는 것 보다는 나을 수 있습니다.
이 방법이 효과를 보려면 예외를 무시한 후에도 프로그램이 신뢰성 있게 실행 가능해야합니다.
안그러면 이렇게 예외를 먹방해버리면 안됩니다.. 처리해 주어야죠
4. 3번보다 더 좋은 해결법
3번의 방법들 모두 사소한 문제를 가지고 있습니다.
close의 예외에 대한 대책이 전무한 상태이기 때문입니다.
아래처럼 코드를 짜게 되면 DBConn의 소멸자에서 DoConn의 사용자에게 close의 호출을 떠넘기게 됩니다.
class DBConnection {
public:
static DBConnection create() {
// DB 연결 객체 생성 및 반환
return DBConnection();
}
void close() {
// DB 연결을 닫는 코드
// 실패 시 예외 발생 가능
std::cout << "DB 연결 닫힘" << std::endl;
}
};
// DBConnection을 관리하는 래퍼 클래스
class DBConn {
public:
DBConn(const DBConnection& connection)
: db(connection), closed(false) {}
// 명시적 close 메서드 - 사용자가 호출할 수 있음
void close() {
db.close();
closed = true;
}
// 소멸자 - 안전망 역할
~DBConn() {
if (!closed) {
try {
db.close();
}
catch (...) {
// 로그 기록이나 다른 오류 처리
// 하지만 예외는 전파하지 않음
std::cerr << "소멸자에서 DB 연결 닫기 실패" << std::endl;
}
}
}
private:
DBConnection db;
bool closed;
};
이곳에서 핵심은 예외는 소멸자가 아닌 다른 함수에서 비롯된 것이어야 한다는 점입니다.
예외를 일으키는 소멸자는 마치 시한폭탄과도 같습니다.
불완전한 종료나 미정의 동작의 위험 때문입니다.
이렇게 하면 1차적으로 사용자에게 에러를 처리할 기회를 주는것입니다.
만약 소멸자에서 실행된 Close로 terminate와같은 예외가 발생하게 되면 사용자는 할말이 없어집니다.
왜냐하면 사용자가 Close를 호출하지 않았기 때문이죠.
이것만은 잊지 말자!
- 소멸자에서 예외가 빠져나가면 안된다. 만약 소멸자 안의 호출된 함수가 예외를 던질 가능성이 있으면, 어떤 예외이든지 소멸자에서 모두 받아낸 후에 삼켜 버리든지 프로그램을 끝내야 합니다.
- 어떤 클래스의 연산이 진행되다가 던진 예외에 대해 사용자가 반응해야 할 필요가 있다면, 해당 연산을 제공하는 함수는 반드시 보통의 함수(소멸자가 아닌 함수)이어야 합니다.
추가) C++ 11에서는?
noexcept라는 C++11에서 도입된 specification이 있습니다.
이것은 함수가 예외를 던지지 않음을 알려줍니다.
~MyClass();
~MyClass() noexcept;
noexcept를 쓰지 않더라도 자동으로 넣어줍니다.
만약 이곳에서 예외가 발생하면 어떻게 될까요?
- 스택 풀기(stack unwinding)가 발생하지 않습니다.
- 소멸자가 호출되지 않습니다.
- 메모리 누수나 자원 누수가 발생할 수 있습니다.
- 일반적으로 std::abort()를 호출하여 프로그램을 비정상 종료합니다.
따라서 noexcept 함수(소멸자 포함) 내에서는 예외가 발생할 수 있는 코드를
항상 try-catch 블록으로 감싸서 내부적으로 처리해야 합니다.
그렇지 않으면 std::terminate 함수가 호출되어 프로그램이 종료됩니다.
항목 9
https://yeoul0714.tistory.com/36
[Effective C++] 항목 9: 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자
1. 개요저자는 객체 생성, 소멸중에 가상 함수를 절대 호출하지 말라고 합니다. 그 이유는 무엇일까? 지금부터 알아 보도록 합시다. 2. 문제아래 코드를 보면 Transaction이 가장 최상위 클래스이고
yeoul0714.tistory.com
'Effective C++ > Chapter 2: 생성자, 소멸자 및 대입 연산자' 카테고리의 다른 글
[Effective C++] 항목 10: 대입 연산자는 *this의 참조자를 반환하게 하자 (0) | 2025.04.23 |
---|---|
[Effective C++] 항목 9: 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자 (0) | 2025.04.21 |
[Effective C++] 항목 7: 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자 (0) | 2025.04.18 |
[Effective C++] 항목 6: 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자 (0) | 2025.04.18 |
[Effective C++] 항목 5: C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자 (0) | 2025.04.18 |