사용자 정의 예외 계층 구조
Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 104개 중 82번째.
표준 라이브러리가 std::runtime_error와 같은 유용한 예외 클래스를 제공하지만, 실제 애플리케이션에서는 사용자 정의 예외 계층 구조를 만드는 것이 종종 유리합니다. 표준 예외를 상속하는 고유한 예외 클래스를 정의함으로써, 추가적인 컨텍스트를 포함하고 더 정밀한 오류 처리를 가능하게 하는 도메인 특화된 오류 유형을 생성할 수 있습니다.
사용자 정의 예외 클래스는 일반적으로 std::exception 또는 그 파생 클래스 중 하나를 상속받고 what() 메서드를 오버라이드합니다:
#include <exception>
#include <string>
class DatabaseException : public std::exception {
std::string message;
public:
DatabaseException(const std::string& msg) : message(msg) {}
const char* what() const noexcept override { return message.c_str(); }
};
class ConnectionException : public DatabaseException {
public:
ConnectionException(const std::string& host)
: DatabaseException("Failed to connect to: " + host) {}
};
class QueryException : public DatabaseException {
public:
QueryException(const std::string& query)
: DatabaseException("Invalid query: " + query) {}
};이 계층 구조를 통해 catch 블록은 다양한 수준의 구체성으로 예외를 처리할 수 있습니다:
try {
// 데이터베이스 작업...
throw ConnectionException("localhost");
} catch (const ConnectionException& e) {
std::cout << "Connection issue: " << e.what() << "\n";
} catch (const DatabaseException& e) {
std::cout << "Database error: " << e.what() << "\n";
} catch (const std::exception& e) {
std::cout << "General error: " << e.what() << "\n";
}what()에 지정된 noexcept 지정자는 중요합니다. 이는 메서드가 예외를 던지지 않음을 보장하며, 이는 기본 클래스 인터페이스에서 요구하는 사항입니다. C++는 첫 번째로 일치하는 핸들러를 사용하므로, catch 블록을 가장 구체적인 것부터 가장 일반적인 것 순으로 배치하세요.
챌린지
쉬움다양한 유형의 파일 관련 오류를 처리하는 사용자 정의 예외 계층 구조를 갖춘 파일 처리 시스템을 구축해 보겠습니다. 기본 예외 클래스에서 상속되는 도메인별 예외를 생성하여, 다양한 수준의 구체성에서 정밀한 오류 처리가 가능하도록 할 것입니다.
코드는 세 개의 파일로 구성됩니다:
FileExceptions.h: 파일 작업을 위한 사용자 정의 예외 계층 구조를 정의합니다.std::exception을 상속받는 기본FileException클래스를 생성합니다. 이 클래스는 오류 메시지를 저장하고noexcept지정자와 함께what()메서드를 재정의해야 합니다.그런 다음 두 개의 파생 예외 클래스를 생성합니다:
FileNotFoundException— 파일 이름을 인자로 받아File not found: [filename]이라는 메시지를 생성합니다.PermissionDeniedException— 파일 이름을 인자로 받아Permission denied: [filename]이라는 메시지를 생성합니다.
각 파생 클래스는 적절한 메시지와 함께 부모 생성자를 호출해야 합니다.
FileProcessor.h: 파일 작업을 시뮬레이션하고 사용자 정의 예외를 던지는FileProcessor클래스를 정의합니다.FileProcessor에는 파일 열기를 시뮬레이션하는openFile(const std::string& filename)메서드가 있어야 합니다:- 파일 이름에
missing이 포함되어 있으면FileNotFoundException을 던집니다. - 파일 이름에
locked가 포함되어 있으면PermissionDeniedException을 던집니다. - 그렇지 않으면
Successfully opened: [filename]문자열을 반환합니다.
- 파일 이름에
main.cpp: 입력에서 단일 파일 이름을 읽습니다.FileProcessor를 생성하고 파일 열기를 시도합니다. 가장 구체적인 것부터 가장 일반적인 것 순으로 정렬된 핸들러가 있는 try-catch 블록을 사용합니다:FileNotFoundException을 캐치하여File Error: [message]를 출력합니다.PermissionDeniedException을 캐치하여Access Error: [message]를 출력합니다.FileException을 캐치하여General File Error: [message]를 출력합니다.
예외가 발생하지 않으면
openFile()에서 반환된 성공 메시지를 출력합니다.
예를 들어, 입력이 report.txt인 경우:
Successfully opened: report.txt입력이 missing_data.csv인 경우:
File Error: File not found: missing_data.csv입력이 locked_config.ini인 경우:
Access Error: Permission denied: locked_config.ini이 챌린지는 사용자 정의 예외 계층 구조를 통해 적절한 세부 수준에서 오류를 처리하는 방법을 보여줍니다. 정밀한 처리가 필요할 때는 특정 예외를 캐치하고, 모든 파일 오류를 균일하게 처리하고 싶을 때는 기본 예외를 캐치할 수 있습니다.
치트 시트
사용자 정의 예외 클래스는 std::exception 또는 그 파생 클래스로부터 상속받으며 what() 메서드를 오버라이드합니다:
#include <exception>
#include <string>
class DatabaseException : public std::exception {
std::string message;
public:
DatabaseException(const std::string& msg) : message(msg) {}
const char* what() const noexcept override { return message.c_str(); }
};사용자 정의 기본 예외로부터 상속받아 예외 계층 구조를 생성합니다:
class ConnectionException : public DatabaseException {
public:
ConnectionException(const std::string& host)
: DatabaseException("Failed to connect to: " + host) {}
};가장 구체적인 것부터 가장 일반적인 것 순으로 catch 블록을 배치하여 다양한 수준의 구체성으로 예외를 처리합니다:
try {
throw ConnectionException("localhost");
} catch (const ConnectionException& e) {
std::cout << "Connection issue: " << e.what() << "\n";
} catch (const DatabaseException& e) {
std::cout << "Database error: " << e.what() << "\n";
} catch (const std::exception& e) {
std::cout << "General error: " << e.what() << "\n";
}what()에 지정된 noexcept 지정자는 메서드가 예외를 던지지 않음을 보장하며, 이는 기본 클래스 인터페이스에서 요구하는 사항입니다.
직접 해보기
#include <iostream>
#include <string>
#include "FileExceptions.h"
#include "FileProcessor.h"
using namespace std;
int main() {
string filename;
cin >> filename;
FileProcessor processor;
// TODO: 예외 처리를 위해 try-catch 블록을 사용하세요
// 가장 구체적인 것부터 가장 일반적인 순서로 핸들러를 배치하세요:
// 1. FileNotFoundException 처리 - 출력: "File Error: [message]"
// 2. PermissionDeniedException 처리 - 출력: "Access Error: [message]"
// 3. FileException 처리 - 출력: "General File Error: [message]"
// 예외가 발생하지 않으면, openFile()의 성공 메시지를 출력하세요
try {
// TODO: processor.openFile()을 호출하고 결과를 처리하세요
}
// TODO: 가장 구체적인 것부터 가장 일반적인 순서로 catch 블록을 추가하세요
return 0;
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.