# C언어 (11)

## 1\. 템플릿

### 1-1. 템플릿의 정의

템플릿(Template)은 **타입을 매개변수로 받아서 함수나 클래스를 자동으로 생성하는 틀** 이다. "어떤 타입이든 동작하는 코드"를 한 번만 작성하면, 컴파일러가 실제 사용되는 타입에 맞춰 코드를 찍어낸다.

### 1-2. 템플릿의 필요성

`int` 두 개를 비교해서 큰 값을 반환하는 함수를 만들었다고 하자.

```cpp
int getMax(int a, int b) {
    return (a > b) ? a : b;
}
```

그런데 `double`에도 같은 함수가 필요하다. `string`에도 필요하다. 로직은 완전히 같은데 타입만 다르다.

```cpp
int getMax(int a, int b) { return (a > b) ? a : b; }
double getMax(double a, double b) { return (a > b) ? a : b; }
string getMax(string a, string b) { return (a > b) ? a : b; }
```

오버로딩으로 해결할 수는 있지만, 본질적으로 **같은 코드를 타입만 바꿔서 반복 작성** 하는 것이다. 새로운 타입이 추가될 때마다 함수를 하나 더 만들어야 한다. 이 문제를 해결하는 것이 템플릿이다.

### 1-3. 템플릿의 구문 구조

템플릿은 `template` 키워드와 꺾쇠(`< >`) 안에 **타입 매개변수** 를 지정해서 만든다.

```cpp
template <typename T>
```

`T`는 관례적으로 사용하는 이름이지만, `Type`, `U`, `DataType` 등 아무 이름이나 쓸 수 있다. `typename` 대신 `class`를 써도 동일하게 동작한다. `template <class T>`와 `template <typename T>`는 같은 의미다.

타입 매개변수는 여러 개 사용할 수도 있다.

```cpp
template <typename T, typename U>
```

### 1-4. 함수 템플릿

함수 템플릿은 **타입에 독립적인 함수** 를 만든다. 아까의 `getMax` 함수를 템플릿으로 만들면 이렇다.

```cpp
template <typename T>
T getMax(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    cout << getMax(10, 20) << endl;            // T가 int로 추론
    cout << getMax(3.14, 2.72) << endl;        // T가 double로 추론
    cout << getMax<string>("abc", "xyz") << endl;  // 명시적으로 타입 지정
    return 0;
}
```

`getMax(10, 20)`을 호출하면 컴파일러가 인자의 타입을 보고 `T`를 `int`로 **추론** 한다. 그리고 `int getMax(int a, int b)` 함수를 자동으로 생성한다. `getMax(3.14, 2.72)`를 호출하면 `double` 버전이 자동으로 만들어진다.

꺾쇠로 타입을 직접 지정할 수도 있다. `getMax<string>("abc", "xyz")`처럼 쓰면 "T를 `string`으로 쓰겠다"고 명시하는 것이다.

중요한 점은 템플릿 자체가 함수가 아니라 **함수를 만들어내는 틀** 이라는 것이다. 실제 함수는 특정 타입으로 호출될 때 컴파일러가 생성한다. 이 과정을 **템플릿 인스턴스화(instantiation)** 라고 한다.

여러 타입 매개변수를 사용하는 예시도 보자.

```cpp
template <typename T, typename U>
void printPair(T first, U second) {
    cout << first << ", " << second << endl;
}

int main() {
    printPair(1, 3.14);         // T=int, U=double
    printPair("Hello", 42);     // T=const char*, U=int
    return 0;
}
```

### 1-5. 클래스 템플릿

클래스 템플릿은 **타입에 독립적인 클래스** 를 만든다. 어떤 타입의 데이터든 담을 수 있는 범용 컨테이너를 만들 때 유용하다.

```cpp
template <typename T>
class Box {
private:
    T value;

public:
    Box(T v) : value(v) {}

    T getValue() {
        return value;
    }

    void setValue(T v) {
        value = v;
    }
};

int main() {
    Box<int> intBox(42);
    Box<string> strBox("Hello");
    Box<double> dblBox(3.14);

    cout << intBox.getValue() << endl;   // 42
    cout << strBox.getValue() << endl;   // Hello
    cout << dblBox.getValue() << endl;   // 3.14
    return 0;
}
```

함수 템플릿과 달리 클래스 템플릿은 **타입을 명시적으로 지정해야** 한다. `Box<int>`, `Box<string>`처럼 꺾쇠 안에 타입을 넣어서 객체를 생성한다.

좀 더 실용적인 예시로, 간단한 스택을 템플릿으로 만들어보자.

```cpp
template <typename T>
class Stack {
private:
    T *data;
    int top;
    int capacity;

public:
    Stack(int cap) : capacity(cap), top(-1) {
        data = new T[capacity];
    }

    ~Stack() {
        delete[] data;
    }

    void push(T value) {
        if (top < capacity - 1) {
            data[++top] = value;
        }
    }

    T pop() {
        if (top >= 0) {
            return data[top--];
        }
        throw "스택이 비어있습니다";
    }

    bool isEmpty() {
        return top == -1;
    }
};

int main() {
    Stack<int> intStack(10);
    intStack.push(1);
    intStack.push(2);
    intStack.push(3);
    cout << intStack.pop() << endl;  // 3

    Stack<string> strStack(5);
    strStack.push("Hello");
    strStack.push("World");
    cout << strStack.pop() << endl;  // World

    return 0;
}
```

`Stack<int>`는 정수를 담는 스택이 되고, `Stack<string>`은 문자열을 담는 스택이 된다. 코드는 하나지만 타입에 따라 다른 클래스가 자동 생성된다. JavaScript의 배열이 `push()`/`pop()`으로 스택처럼 동작하는 것과 비슷하지만, C++ 템플릿은 타입 안전성까지 보장한다.

실제로 C++ 표준 라이브러리(STL)의 `vector`, `stack`, `queue`, `map` 같은 컨테이너들이 전부 클래스 템플릿으로 만들어져 있다. `vector<int>`, `vector<string>` 형태로 쓰는 것이 바로 클래스 템플릿의 인스턴스화다.

* * *

## 2\. 예외처리

프로그램 실행 중에는 예상치 못한 상황이 발생할 수 있다. 0으로 나누기, 메모리 할당 실패, 배열 범위 초과, 파일 열기 실패 등이 그렇다. 이런 상황을 **예외(Exception)** 라 하고, 이를 처리하는 메커니즘이 **예외처리** 다.

C에서는 반환값으로 에러를 알렸다. 함수가 `-1`이나 `NULL`을 반환하면 에러인 식이다. 이 방식은 반환값을 매번 체크해야 하고, 정상 반환값과 에러 반환값을 구분하기 어려울 때가 있다.

C++에서는 `try`, `catch`, `throw` 키워드로 예외를 처리한다.

```cpp
try {
    // 예외가 발생할 수 있는 코드
} catch (예외타입 변수) {
    // 예외 처리 코드
}
```

`throw`는 예외를 **던지는** 것이고, `try` 블록 안에서 발생한 예외를 `catch` 블록이 **잡는** 것이다.

```cpp
double divide(int a, int b) {
    if (b == 0) {
        throw "0으로 나눌 수 없습니다";  // 예외 던지기
    }
    return (double)a / b;
}

int main() {
    try {
        cout << divide(10, 2) << endl;   // 5
        cout << divide(10, 0) << endl;   // 여기서 예외 발생
        cout << "이 줄은 실행되지 않음" << endl;
    } catch (const char *msg) {
        cout << "에러: " << msg << endl;  // 에러: 0으로 나눌 수 없습니다
    }

    cout << "프로그램 계속 실행" << endl;
    return 0;
}
```

예외가 발생하면 `try` 블록의 나머지 코드는 건너뛰고 바로 `catch` 블록으로 점프한다. 예외처리 후 프로그램은 `catch` 블록 이후부터 정상적으로 계속 실행된다.

여러 종류의 예외를 다르게 처리할 수도 있다.

```cpp
try {
    // ...
} catch (int e) {
    cout << "정수 에러 코드: " << e << endl;
} catch (const char *msg) {
    cout << "메시지: " << msg << endl;
} catch (...) {
    cout << "알 수 없는 예외" << endl;  // 모든 예외를 잡음
}
```

`catch (...)`는 앞의 `catch`에서 잡지 못한 모든 종류의 예외를 잡는다. 마지막에 두는 것이 일반적이다.

실무에서는 문자열이나 정수 대신 **예외 클래스** 를 정의해서 사용한다.

```cpp
class DivideByZeroException {
private:
    string message;

public:
    DivideByZeroException(string msg) : message(msg) {}

    string getMessage() const {
        return message;
    }
};

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

int main() {
    try {
        divide(10, 0);
    } catch (const DivideByZeroException &e) {
        cout << e.getMessage() << endl;
    }
    return 0;
}
```

C++ 표준 라이브러리는 `<stdexcept>` 헤더에 `runtime_error`, `invalid_argument`, `out_of_range` 같은 예외 클래스를 제공한다. 이들은 모두 `exception` 클래스를 상속받는다.

```cpp
#include <stdexcept>

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

try {
    divide(10, 0);
} catch (const exception &e) {
    cout << e.what() << endl;  // what()으로 메시지 확인
}
```

예외처리의 핵심 원칙은 **정상 흐름과 에러 처리를 분리** 하는 것이다. `try` 블록에는 정상적인 로직만 두고, 에러 대응은 `catch` 블록에 모아둔다. 코드의 가독성이 높아지고, 에러 처리를 빼먹을 위험도 줄어든다.

* * *

## 마무리

템플릿은 타입에 독립적인 코드를 작성하게 해주는 C++의 강력한 기능이다. 같은 로직을 타입만 바꿔서 반복 작성하는 문제를 해결하며, STL의 모든 컨테이너가 클래스 템플릿으로 만들어져 있다. 예외처리는 `try`/`catch`/`throw`로 정상 흐름과 에러 처리를 깔끔하게 분리한다. C의 반환값 기반 에러 처리보다 직관적이고 안전하다.
