C++에서 예외(Exception)는 프로그램 실행 중 오류 또는 비정상적인 상황이 발생했을 때 이를 감지하고 적절히 처리하는 메커니즘이다.
예외 처리를 사용하면 프로그램이 비정상적으로 종료되는 것을 방지하고, 오류를 보다 체계적으로 관리할 수 있다.
C++ 예외 처리는 세 가지 주요 키워드로 구성된다.
throw 예외를 발생시킬 때 사용
try 예외 발생 가능성이 있는 코드를 감싸는 블록
catch 예외가 발생했을 때 실행할 코드 블록
try {
// 예외가 발생할 가능성이 있는 코드
throw 42; // 예외 발생
}
catch (int e) {
// 예외 처리 코드
std::cout << "Caught exception: " << e << std::endl;
}
✔ throw → 예외 발생 시, 예외 객체를 전달함
✔ try → 예외 발생 가능성이 있는 코드 블록을 감싸줌
✔ catch → 발생한 예외를 받아 처리
#include <iostream>
void testFunction() {
std::cout << "Throwing an exception..." << std::endl;
throw std::runtime_error("Something went wrong!"); // 예외 발생
}
int main() {
try {
testFunction();
}
catch (const std::runtime_error& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
std::cout << "Program continues..." << std::endl;
return 0;
}
🔹 실행 결과:
Throwing an exception...(예외 발생)
Caught exception: Something went wrong!(해당 예외 타입에 대한 정보를 받아 실행)
Program continues...
✔ 예외가 발생하면 즉시 catch 블록으로 이동하여 처리됨
✔ 예외 처리 후에도 프로그램이 정상적으로 실행됨
예외 타입에 따라 다른 방식으로 처리 가능
try {
throw 42; // 예외 발생
}
catch (int e) {
std::cout << "Caught an integer: " << e << std::endl;
}
catch (const std::runtime_error& e) {
std::cout << "Caught a runtime_error: " << e.what() << std::endl;
}
catch (...) { // 모든 예외를 처리하는 블록 switch의 default처럼
std::cout << "Caught an unknown exception" << std::endl;
}
✔ 특정 예외 타입에 맞는 catch 블록이 실행됨
✔ catch (...)는 모든 예외를 처리하는 블록 (예외 종류를 모를 때 사용)
예외는 함수를 넘어서 상위 함수로 전파될 수 있다.
#include <iostream>
void functionA() {
throw std::runtime_error("Error in functionA");
}
void functionB() {
functionA(); // functionA에서 예외 발생
}
int main() {
try {
functionB(); // functionB가 호출됨
}
catch (const std::runtime_error& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
}
🔹 실행 결과:
Caught exception: Error in functionA
✔ 예외가 발생하면 functionA → functionB → main으로 전파됨
✔ main에서 예외가 처리되지 않으면 프로그램이 비정상 종료됨
만약 부모,자식 클래스에 대해서 받을 경우 catch순서에 부모 클래스를 먼저 둘 경우
Parent& p = Child()✅
Child& p = Parent()❌
도 가능하므로 자식클래스 예외타입에 대해서도 부모 클래스에 대한 catch가 실행되므로 자식 클래스를 앞에 두는 것이 좋다.
스택 풀기(Stack Unwinding)란 예외가 발생하여 함수가 종료될 때, 지역 변수들이 자동으로 소멸되는 과정을 의미한다.
📌 스택 풀기의 원리:
예외 발생 시, 현재 함수가 즉시 종료됨.
지역 변수들의 소멸자가 자동으로 호출됨.
예외가 상위 함수로 전파되면서, 그 과정에서 할당된 모든 지역 변수들이 해제됨.
catch 블록에서 예외를 처리하면 예외 전파가 멈춤.
📌 예제 (소멸자 호출 확인)
#include <iostream>
struct Test {
std::string name;
Test(std::string n) : name(n) { std::cout << name << " created\n"; }
~Test() { std::cout << name << " destroyed\n"; }
};
void functionA() {
Test a("A");
throw std::runtime_error("Exception in functionA");
}
void functionB() {
Test b("B");
functionA();
}
int main() {
try {
functionB();
}
catch (const std::runtime_error& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
}
🔹 실행 결과:
B created
A created
A destroyed
B destroyed
Caught exception: Exception in functionA
✔ 예외가 발생하면 지역 변수들이 역순으로 소멸됨.
✔ 스택 풀기가 동작하여 자원 누수가 방지됨.
유의할 점 -> 생성자에서 예외 발생 시 소멸자가 호출되지 않음!@
C++에서는 std::exception을 상속받아 사용자 정의 예외 클래스를 만들 수 있다.
#include <iostream>
#include <stdexcept>
class MyException : public std::exception {
public:
const char* what() const noexcept override {
return "Custom exception occurred!";
}
};
void test() {
throw MyException();
}
int main() {
try {
test();
}
catch (const MyException& e) {
std::cout << "Caught: " << e.what() << std::endl;
}
}
✔ std::exception을 상속받아 what()을 오버라이드하여 예외 메시지를 정의 가능.
✔ 사용자 정의 예외를 통해 더 세밀한 예외 처리가 가능.
what()->예외 발생 시 해당 예외에 대한 설명 메시지(문자열)를 반환하는 역할
noexcept 키워드를 사용하면 함수에서 예외가 발생하지 않음을 명시할 수 있다.
void safeFunction() noexcept {
std::cout << "This function never throws exceptions.\n";
}
✔ noexcept가 붙은 함수에서 예외가 발생하면 프로그램이 강제 종료됨.
✔ noexcept가 붙었다고 해서 절대 예외를 발생시키지 않는 것은 아니다.
-> 컴파일러가 예외를 발생시키지 않는다고 생각하고 컴파일 하는 것임.
-✔ 예외를 절대 발생시키지 않는 함수에 사용하면 최적화에 도움이 됨.
✔ C++11 부터 소멸자는 기본적으로 noexcept임.