newclass
2025. 3. 28. 08:58
고급 주제
이 챕터에서는, C++의 고급 주제와 최신 기능들을 살펴봅니다. 이러한 고급 기능들은 더욱 효율적이고 안전한 코드를 작성하는 데 도움이 됩니다.
9.1 예외 처리(Exception Handling)
예외 처리는 프로그램 실행 중 발생하는 오류를 처리하는 메커니즘입니다.
9.1.1 예외의 기본 개념
#include <iostream>
#include <stdexcept> // 표준 예외 클래스
double divide(double a, double b) {
if (b == 0.0) {
throw std::invalid_argument("0으로 나눌 수 없습니다");
}
return a / b;
}
int main() {
try {
double result = divide(10.0, 2.0);
std::cout << "결과: " << result << std::endl;
result = divide(10.0, 0.0); // 이 줄에서 예외 발생
std::cout << "이 줄은 실행되지 않습니다." << std::endl;
}
catch (const std::invalid_argument& e) {
std::cerr << "에러 발생: " << e.what() << std::endl;
}
std::cout << "프로그램이 계속 실행됩니다." << std::endl;
return 0;
}
9.1.2 표준 예외 클래스
C++ 표준 라이브러리는 다양한 예외 클래스를 제공합니다:
#include <iostream>
#include <vector>
#include <stdexcept>
#include <new>
#include <typeinfo>
int main() {
// 논리 에러
try {
throw std::logic_error("논리적 오류가 발생했습니다");
}
catch (const std::logic_error& e) {
std::cerr << "Logic error: " << e.what() << std::endl;
}
// 범위 오류
try {
std::vector<int> vec(5);
vec.at(10) = 100; // 범위를 벗어난 접근
}
catch (const std::out_of_range& e) {
std::cerr << "Out of range error: " << e.what() << std::endl;
}
// 잘못된 인자
try {
throw std::invalid_argument("잘못된 인자가 전달되었습니다");
}
catch (const std::invalid_argument& e) {
std::cerr << "Invalid argument: " << e.what() << std::endl;
}
// 런타임 에러
try {
throw std::runtime_error("실행 시간 오류가 발생했습니다");
}
catch (const std::runtime_error& e) {
std::cerr << "Runtime error: " << e.what() << std::endl;
}
// 메모리 할당 실패
try {
throw std::bad_alloc();
}
catch (const std::bad_alloc& e) {
std::cerr << "Memory allocation failed: " << e.what() << std::endl;
}
// 타입 캐스팅 실패
try {
// dynamic_cast가 실패하면 bad_cast 예외가 발생할 수 있음
throw std::bad_cast();
}
catch (const std::bad_cast& e) {
std::cerr << "Bad cast: " << e.what() << std::endl;
}
return 0;
}
9.1.3 사용자 정의 예외 클래스
특정 오류 상황에 맞게 자신만의 예외 클래스를 정의할 수 있습니다:
#include <iostream>
#include <stdexcept>
#include <string>
// 사용자 정의 예외 클래스 (std::exception 상속)
class DatabaseException : public std::exception {
private:
std::string message;
int errorCode;
public:
DatabaseException(const std::string& msg, int code)
: message(msg), errorCode(code) {}
// what() 메서드 오버라이딩
const char* what() const noexcept override {
return message.c_str();
}
int getErrorCode() const {
return errorCode;
}
};
// 더 구체적인 예외 클래스
class ConnectionException : public DatabaseException {
public:
ConnectionException(const std::string& msg, int code)
: DatabaseException("연결 오류: " + msg, code) {}
};
class QueryException : public DatabaseException {
public:
QueryException(const std::string& msg, int code)
: DatabaseException("쿼리 오류: " + msg, code) {}
};
// 데이터베이스 연결 시뮬레이션
void connectToDatabase(const std::string& connectionString) {
if (connectionString.empty()) {
throw ConnectionException("연결 문자열이 비어 있습니다", 1001);
}
if (connectionString == "invalid") {
throw ConnectionException("잘못된 연결 설정입니다", 1002);
}
std::cout << "데이터베이스에 연결되었습니다." << std::endl;
}
// 쿼리 실행 시뮬레이션
void executeQuery(const std::string& query) {
if (query.empty()) {
throw QueryException("쿼리가 비어 있습니다", 2001);
}
if (query == "DELETE FROM users") {
throw QueryException("사용자 보호를 위해 이 쿼리는 실행할 수 없습니다", 2002);
}
std::cout << "쿼리 실행 성공: " << query << std::endl;
}
int main() {
try {
// 데이터베이스 연결 시도
connectToDatabase("valid_connection");
// 쿼리 실행 시도
executeQuery("SELECT * FROM users");
executeQuery("DELETE FROM users"); // 이 쿼리는 오류를 발생시킵니다
}
catch (const ConnectionException& e) {
std::cerr << "연결 실패: " << e.what() << " (코드: " << e.getErrorCode() << ")" << std::endl;
}
catch (const QueryException& e) {
std::cerr << "쿼리 실패: " << e.what() << " (코드: " << e.getErrorCode() << ")" << std::endl;
}
catch (const DatabaseException& e) {
std::cerr << "데이터베이스 오류: " << e.what() << " (코드: " << e.getErrorCode() << ")" << std::endl;
}
catch (const std::exception& e) {
std::cerr << "일반 오류: " << e.what() << std::endl;
}
catch (...) {
std::cerr << "알 수 없는 오류가 발생했습니다." << std::endl;
}
return 0;
}
9.1.4 예외 재던지기(Exception Rethrow)
현재 catch 블록에서 예외를 처리한 후 상위 호출자에게 다시 전달할 수 있습니다:
#include <iostream>
#include <stdexcept>
void processPositiveNumber(int num) {
if (num < 0) {
throw std::invalid_argument("음수는 처리할 수 없습니다");
}
std::cout << "값 처리 중: " << num << std::endl;
}
void processData(int data) {
try {
processPositiveNumber(data);
}
catch (const std::exception& e) {
std::cout << "processData에서 예외 포착: " << e.what() << std::endl;
// 일부 정리 작업 수행
std::cout << "부분 정리 작업 수행..." << std::endl;
// 예외 재던지기
throw; // 같은 예외를 상위 호출자에게 전달
}
}
int main() {
try {
processData(10); // 정상 처리됨
processData(-5); // 예외 발생
}
catch (const std::exception& e) {
std::cerr << "main에서 예외 포착: " << e.what() << std::endl;
}
return 0;
}
9.1.5 함수 예외 명세(Exception Specification)
함수가 어떤 예외를 던질 수 있는지 지정할 수 있습니다. 하지만 C++11부터 이 기능은 더 이상 사용되지 않으며, C++17에서는 제거되었습니다. 대신 noexcept 명세를 사용합니다:
#include <iostream>
#include <stdexcept>
// 예외를 던지지 않는 함수 (C++11)
void safeFunction() noexcept {
std::cout << "이 함수는 예외를 던지지 않습니다." << std::endl;
// throw std::runtime_error("오류"); // 컴파일 오류는 없지만, 런타임에 std::terminate가 호출됨
}
// 조건부 noexcept
template <typename T>
T add(T a, T b) noexcept(noexcept(a + b)) {
return a + b;
}
// noexcept 연산자로 함수가 예외를 던지지 않는지 확인
void checkExceptions() {
std::cout << "safeFunction은 noexcept인가? "
<< std::boolalpha << noexcept(safeFunction()) << std::endl;
std::cout << "add<int>는 noexcept인가? "
<< noexcept(add<int>(1, 2)) << std::endl;
}
int main() {
try {
safeFunction();
checkExceptions();
}
catch (...) {
std::cerr << "예외가 발생했습니다." << std::endl;
}
return 0;
}