Learning
레슨 9 / 10·4개 토픽

템플릿과 예외 처리

함수 템플릿

템플릿은 타입에 독립적인 코드를 작성할 수 있게 해주는 C++의 제네릭 프로그래밍 도구입니다. 컴파일 시점에 사용된 타입에 맞는 코드가 자동으로 생성됩니다.

cpp
#include <iostream>
#include <string>
#include <vector>
using namespace std;

// 함수 템플릿
template<typename T>
T maxValue(T a, T b) {
    return (a > b) ? a : b;
}

// 여러 타입 매개변수
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
    return a + b;
}

// 템플릿 특수화
template<>
const char* maxValue<const char*>(const char* a, const char* b) {
    return (strcmp(a, b) > 0) ? a : b;
}

int main() {
    cout << maxValue(3, 7) << endl;         // 7 (int)
    cout << maxValue(3.14, 2.71) << endl;   // 3.14 (double)
    cout << maxValue("abc", "xyz") << endl;  // xyz (특수화)

    cout << add(3, 4.5) << endl;  // 7.5 (int + double)
    return 0;
}

클래스 템플릿

cpp
#include <iostream>
#include <stdexcept>
using namespace std;

// 클래스 템플릿 — 제네릭 스택
template<typename T, int MaxSize = 100>
class Stack {
    T data[MaxSize];
    int top;

public:
    Stack() : top(-1) {}

    void push(const T& item) {
        if (top >= MaxSize - 1)
            throw overflow_error("스택 오버플로");
        data[++top] = item;
    }

    T pop() {
        if (top < 0)
            throw underflow_error("스택 언더플로");
        return data[top--];
    }

    bool empty() const { return top < 0; }
    int size() const { return top + 1; }
};

int main() {
    Stack<int, 50> intStack;      // int 스택, 최대 50개
    Stack<string> strStack;       // string 스택, 기본 100개

    intStack.push(10);
    intStack.push(20);
    cout << intStack.pop() << endl; // 20

    strStack.push("Hello");
    strStack.push("World");
    cout << strStack.pop() << endl; // World
    return 0;
}

예외 처리

C++의 예외 처리는 try/catch/throw로 구성됩니다. std::exception을 상속하여 커스텀 예외를 만들 수 있으며, 소멸자에서 리소스를 해제하는 RAII 패턴과 함께 사용하면 예외 안전한 코드를 작성할 수 있습니다.

cpp
#include <iostream>
#include <stdexcept>
#include <string>
#include <fstream>
using namespace std;

// 커스텀 예외
class ValidationError : public runtime_error {
    int errorCode;
public:
    ValidationError(const string& msg, int code)
        : runtime_error(msg), errorCode(code) {}
    int getCode() const { return errorCode; }
};

int divide(int a, int b) {
    if (b == 0)
        throw invalid_argument("0으로 나눌 수 없습니다");
    return a / b;
}

void validateAge(int age) {
    if (age < 0 || age > 150)
        throw ValidationError("잘못된 나이: " + to_string(age), 1001);
}

int main() {
    // 기본 try/catch
    try {
        cout << divide(10, 0) << endl;
    } catch (const invalid_argument& e) {
        cerr << "에러: " << e.what() << endl;
    }

    // 커스텀 예외
    try {
        validateAge(200);
    } catch (const ValidationError& e) {
        cerr << "검증 실패 [" << e.getCode() << "]: "
             << e.what() << endl;
    }

    // 여러 catch 블록
    try {
        throw runtime_error("런타임 에러 발생");
    } catch (const runtime_error& e) {
        cerr << "런타임: " << e.what() << endl;
    } catch (const exception& e) {
        cerr << "일반: " << e.what() << endl;
    } catch (...) {
        cerr << "알 수 없는 예외" << endl;
    }

    return 0;
}

RAII 패턴

cpp
#include <iostream>
#include <fstream>
#include <mutex>
using namespace std;

// RAII — Resource Acquisition Is Initialization
// 리소스 획득을 객체 초기화에, 해제를 소멸자에 맡김

// RAII 파일 래퍼
class FileHandler {
    ofstream file;
public:
    FileHandler(const string& filename) : file(filename) {
        if (!file.is_open())
            throw runtime_error("파일 열기 실패: " + filename);
        cout << "파일 열림: " << filename << endl;
    }

    ~FileHandler() {
        if (file.is_open()) {
            file.close();
            cout << "파일 닫힘" << endl;
        }
    }

    void write(const string& text) {
        file << text << endl;
    }
};

// RAII 뮤텍스 잠금 (std::lock_guard와 동일 원리)
class LockGuard {
    mutex& mtx;
public:
    LockGuard(mutex& m) : mtx(m) { mtx.lock(); }
    ~LockGuard() { mtx.unlock(); }
};

int main() {
    // 예외가 발생해도 소멸자가 호출되어 파일이 닫힘
    try {
        FileHandler fh("output.txt");
        fh.write("Hello, RAII!");
        // 블록 종료 시 ~FileHandler() 자동 호출
    } catch (const exception& e) {
        cerr << e.what() << endl;
    }

    // 표준 라이브러리의 RAII 도구들
    // unique_ptr — 동적 메모리 자동 해제
    // lock_guard — 뮤텍스 자동 해제
    // fstream — 파일 자동 닫기
    return 0;
}
  • template — 타입 매개변수를 선언하여 제네릭 코드 작성
  • 템플릿 특수화 — 특정 타입에 대해 별도의 구현 제공
  • try/catch/throw — 예외를 발생시키고 처리하는 메커니즘
  • std::exception — 모든 표준 예외의 기반 클래스
  • what() — 예외 메시지를 반환하는 가상 함수
  • RAII — 생성자에서 리소스 획득, 소멸자에서 리소스 해제
  • catch(...) — 모든 타입의 예외를 잡는 범용 핸들러
💡

RAII는 C++에서 가장 중요한 관용구 중 하나입니다. 직접 new/delete를 사용하는 대신 unique_ptr, shared_ptr 같은 스마트 포인터를 사용하고, 뮤텍스는 lock_guard로 관리하세요. 예외가 발생해도 리소스 누수를 방지할 수 있습니다.