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;
}