C++ 예외 처리 제대로 이해하기: try-catch 문법과 활용법
🚨 예외 상황에서도 멈추지 않는 안전한 프로그램을 만드는 법
안녕하세요!
오늘은 C++ 개발자라면 반드시 이해하고 있어야 할 예외 처리(Exception Handling)에 대해 알아보려 합니다.
프로그래밍을 하다 보면 사용자 입력 오류, 파일 접근 실패, 메모리 부족 등 다양한 예외 상황을 만나게 됩니다.
이때 아무런 조치 없이 프로그램이 종료된다면 사용자에게는 매우 불편한 경험이 되겠죠.
이러한 문제를 방지하고, 안정성을 확보하기 위해 try-catch 구문은 꼭 필요합니다.
이번 글에서는 C++의 예외 처리 기본 문법과 동작 구조부터 시작해서, 실무에서 자주 발생하는 예외들을 어떻게 처리하면 좋을지 자세히 다뤄볼 예정입니다.
또한 throw 키워드의 쓰임, catch 블록에서 다양한 타입을 다루는 방법, 사용자 정의 예외 클래스까지 예제를 통해 쉽게 설명해드릴게요.
예외 처리를 올바르게 이해하면 프로그램이 훨씬 더 견고해지고, 디버깅도 쉬워집니다!
📋 목차
🚧 예외 처리가 필요한 이유
C++ 프로그램을 작성하다 보면 사용자의 입력 실수나 외부 리소스 접근 실패 등 예상하지 못한 문제가 발생할 수 있습니다.
예를 들어 잘못된 경로로 파일을 열려고 하거나, 0으로 나누는 계산을 수행할 경우, 프로그램이 강제 종료될 수 있습니다.
이러한 상황에서 아무런 예외 처리가 없다면 사용자는 오류 메시지도 보지 못한 채 프로그램이 꺼져버리고 말아요.
더 심각한 경우, 저장되지 않은 데이터가 사라지거나 보안 문제가 생길 수도 있습니다.
- ❌없는 파일을 열려고 해서 ifstream 실패
- 🧮0으로 나누기로 인한 런타임 에러
- 🔍배열 범위를 벗어난 out-of-bound 접근
이런 문제를 방지하기 위해 C++에서는 try-catch를 통한 예외 처리를 제공합니다.
예외 발생이 예상되는 구간을 감싸고, 문제가 생겼을 때 사용자에게 안내 메시지를 출력하거나, 안전한 종료 처리를 할 수 있어요.
💬 예외 처리는 단순한 에러 대응이 아니라, 사용자 경험과 시스템 안정성을 위한 필수적인 기능입니다.
이제 다음 단계에서는 실제로 C++에서 예외를 어떻게 선언하고 처리하는지를 try, throw, catch 키워드를 중심으로 살펴보겠습니다.
🧱 try, throw, catch 기본 구조
C++에서 예외 처리는 try, throw, catch 키워드를 통해 구현됩니다.
예외가 발생할 수 있는 코드를 try 블록 안에 작성하고, 문제가 생기면 throw 키워드를 이용해 예외를 발생시키며, catch 블록에서 그 예외를 받아 처리하는 구조입니다.
다음은 가장 기본적인 예외 처리 예제입니다.
#include <iostream>
int main() {
try {
int a = 10;
int b = 0;
if (b == 0) {
throw "0으로 나눌 수 없습니다!";
}
std::cout << a / b << std::endl;
}
catch (const char* msg) {
std::cerr << "예외 발생: " << msg << std::endl;
}
return 0;
}
위 코드는 0으로 나눗셈을 시도하기 전 예외를 발생시키고, 그 메시지를 catch 블록에서 받아 출력합니다.
이러한 방식으로 프로그램은 중단되지 않고, 예외를 처리하며 계속 흐름을 유지할 수 있어요.
- 🧱try 블록: 예외 발생 가능성이 있는 코드 작성
- 📤throw: 문제가 발생했을 때 예외를 던짐
- 🎯catch: 던져진 예외를 받아 처리하는 영역
💡 TIP: catch (...) {} 문법을 사용하면 어떤 타입의 예외든 모두 받아 처리할 수 있어요. 하지만 남용은 주의해야 합니다.
이제 다음 단계에서는 다양한 타입의 예외를 어떻게 처리할 수 있는지, catch 문을 활용한 예제를 중심으로 알아보겠습니다.
🪓 다양한 예외 타입 처리하기
C++에서는 다양한 타입의 값을 예외로 던질 수 있습니다.
가장 기본적인 문자열 상수부터 정수형, 사용자 정의 클래스까지 어떤 값이든 throw로 전달할 수 있고, catch에서는 해당 타입에 맞춰 처리하면 됩니다.
아래는 서로 다른 타입의 예외를 어떻게 처리하는지 보여주는 예제입니다.
#include <iostream>
#include <string>
int main() {
try {
throw std::string("문자열 예외 발생!");
}
catch (int e) {
std::cout << "정수 예외: " << e << std::endl;
}
catch (const std::string& s) {
std::cout << "문자열 예외: " << s << std::endl;
}
catch (...) {
std::cout << "기타 알 수 없는 예외 발생" << std::endl;
}
return 0;
}
위 예제에서는 std::string 타입 예외를 발생시키고, 해당 타입을 받는 catch 블록에서 메시지를 출력하고 있어요.
타입이 일치하지 않으면 다음 catch로 넘어가거나, catch (...)에서 포괄적으로 처리됩니다.
- 🔢int 또는 double 등 기본 타입 예외
- 🧾std::string 또는 C 문자열 예외
- 🧱사용자 정의 클래스도 예외로 던질 수 있음
다양한 예외 타입을 구분해서 처리하면 예외 발생 원인에 따라 다른 로직을 적용할 수 있어 더욱 세밀한 예외 제어가 가능합니다.
⚠️ 주의: catch 블록의 순서에 따라 상위 타입이 먼저 올 경우, 하위 타입은 실행되지 않을 수 있으니 순서에 유의하세요.
다음 단계에서는 예외를 더 유연하게 활용하기 위한 사용자 정의 예외 클래스 구현 방법을 소개해드릴게요.
🧨 사용자 정의 예외 클래스 만들기
C++에서는 표준 예외뿐 아니라 개발자가 직접 사용자 정의 예외 클래스를 만들어 사용할 수 있습니다.
이 방법을 활용하면 더 구체적이고 명확한 예외 처리가 가능해지며, 특히 규모가 큰 프로젝트나 라이브러리 개발 시 유용합니다.
사용자 정의 예외를 만들려면 std::exception 클래스를 상속받고 what() 함수를 오버라이딩하면 됩니다.
#include <iostream>
#include <exception>
class DivideByZeroException : public std::exception {
public:
const char* what() const noexcept override {
return "0으로 나눌 수 없습니다!";
}
};
int main() {
try {
int a = 10;
int b = 0;
if (b == 0) {
throw DivideByZeroException();
}
std::cout << a / b << std::endl;
}
catch (const DivideByZeroException& e) {
std::cerr << "예외 발생: " << e.what() << std::endl;
}
return 0;
}
이 코드는 DivideByZeroException이라는 사용자 정의 예외를 정의하고, 이를 throw하여 처리하는 구조입니다.
표준 예외처럼 what()을 호출하여 에러 메시지를 출력할 수 있으며, 예외 타입에 따라 정확한 catch 블록에서 분기 처리가 가능해집니다.
- 🔧std::exception을 상속
- 💬
what()함수를 오버라이딩 - 🎯예외 발생 시 해당 타입으로 catch 가능
💡 TIP: 여러 종류의 사용자 예외를 만들고 계층적으로 상속하면, 상황별로 더 정교한 예외 처리가 가능합니다.
다음 단계에서는 예외 처리에서 자주 등장하는 개념인 예외 안전성(Exception Safety)에 대해 자세히 알아보겠습니다.
🧯 예외 안전성(Exception Safety) 개념
예외 안전성(Exception Safety)은 예외가 발생하더라도 프로그램의 정상적인 상태를 유지할 수 있도록 보장하는 프로그래밍 설계 개념입니다.
C++처럼 자원 관리가 중요한 언어에서는 특히 신경 써야 할 부분이에요.
예외 안전성은 일반적으로 다음과 같이 3단계 수준으로 구분됩니다.
- 🟢기본 보장(Basic Guarantee): 예외가 발생해도 프로그램은 유효한 상태를 유지
- 🟡강력 보장(Strong Guarantee): 예외가 발생해도 연산 전 상태로 완전히 롤백
- 🔵불변 보장(No-Throw Guarantee): 예외 자체가 절대 발생하지 않음
이러한 보장 수준은 함수나 연산이 예외 발생 시 얼마나 안전하게 동작하는지를 나타내며, 특히 자원 관리(RAII), 스마트 포인터, 트랜잭션 기법과 밀접한 관련이 있습니다.
💬 예외 안전성을 고려하지 않으면 리소스 누수, 상태 불일치, 메모리 오류가 발생할 수 있습니다.
아래는 예외 안전성을 높이기 위한 대표적인 방법입니다.
- 🧼RAII 패턴을 사용하여 자원을 객체에 맡기기
- 🧠std::unique_ptr, shared_ptr 같은 스마트 포인터 활용
- 📦임시 객체 생성 후 교체로 강력 보장 달성
이제 핵심 개념은 모두 익히셨습니다.
다음 단계에서는 지금까지 다룬 내용을 바탕으로 자주 묻는 질문을 통해 복습해볼게요.
❓ 자주 묻는 질문 (FAQ)
예외 처리를 반드시 사용해야 하나요?
try 없이 throw만 사용하면 안 되나요?
catch 블록은 여러 개 작성해도 되나요?
모든 예외를 한 번에 처리하는 방법이 있나요?
사용자 정의 예외는 꼭 std::exception을 상속해야 하나요?
예외 처리는 프로그램 성능에 영향을 주나요?
예외 메시지를 사용자에게 출력해도 괜찮을까요?
예외 대신 if문으로 처리해도 되지 않나요?
📌 프로그램을 안전하게 만드는 예외 처리 요약
이번 글에서는 C++에서의 예외 처리 전반에 대해 체계적으로 살펴보았습니다.
try, throw, catch 키워드를 중심으로 기본 구조를 이해하고, 다양한 예외 타입을 구분해 처리하는 방법, 사용자 정의 예외 클래스 작성법까지 실제 예제와 함께 설명드렸습니다.
또한 예외 발생 시에도 프로그램이 안정성을 유지하도록 돕는 예외 안전성(Exception Safety) 개념과 설계 팁까지 함께 소개해드렸습니다.
예외 처리는 단순히 오류를 막는 것 이상의 역할을 하며, 코드의 견고함과 유지보수성을 높이는 핵심 도구입니다.
이제 여러분도 예외를 두려워하지 않고 적극적으로 활용해보세요.
안정적인 C++ 프로그램을 작성하는 데 확실한 자신감이 생기실 거예요.
🏷️ 관련 태그:C++예외처리, trycatch, throw문법, 예외안전성, exceptionhandling, 사용자정의예외, C++기초, C++실무, 프로그램안정성, 디버깅팁