devseop08 님의 블로그

[OOP] 제네릭과 템플릿 본문

Language/C++

[OOP] 제네릭과 템플릿

devseop08 2025. 7. 31. 02:35

Generic Programming(일반화 프로그래밍)과 템플릿

  • 제네릭 프로그래밍이란 ? : 타입에 관계없이 동작하는 일반적인 코드를 작성하는 방법
    • 예를 들어, 여러 가지 덧셈을 수행하는 함수를 정의할 때, 기존에는 덧셈에 사용되는 인자들의 타입에 따라 개별적으로 함수들을 구현해야 했다.
    • add(int i1, int i2), add(double d1, double d2), add(Point p1, Point p2)
    • 이를 하나의 함수로 구현할 수 있게 하는 기법이 제네릭 프로그래밍 기법이다.
  • 제네릭 프로그래밍의 구현 방법
    • 매크로 사용
    • 함수/ 클래스 템플릿 사용
  • 매크로를 사용한 제네릭 프로그래밍
    • 매크로(#define) : 코드의 단순 대체
#include <iostream> 

#define MAX(a, b) ((a>b)?a:b)

int main() {
    int x = 100;
    int y = 200;
    std::cout << MAX(x, y); // ((x>y)?x:y)
    std::cout << ((x>y) ? x : y);

    double z = 1.234; 
    double w = 3.456;
    std::cout << MAX(z,w); // ((z,w)?z:w)
}
  • 매크로를 사용한 제네릭 프로그래밍 유의사항
    • 코드가 단순히 대체된다는 사실에 유의
    • 매크로문을 괄호로 감싸는 것이 안전
#define SQUARE1(a) a*a
#define SQUARE2(a) (a*a)

result = 100/SQUARE1(5) // expect 4 but result 100 because 100/5*5
result = 100/SQUARE2(5) // expect 4 and result 4 because 100/(5*5)
  • 매크로를 이용한 제네릭 프로그래밍을 지양하는 이유
      1. 매크로는 디버깅이 불가하다.
      1. 매크로의 확장은 사이드 이펙트를 발생시킬 수 있다.
      1. 매크로는 네임스페이스에 선언 불가하여 전역으로 잡힌다. 따라서 충돌 우려가 있다.
      1. 매크로는 미처 인식하지 못한 영역에 영향을 미칠 수 있다.

C++ 함수 템플릿

  • 템플릿을 사용한 max 함수의 구현
      1. 타입명을 지정한 이름(T)으로 대체
      1. 대체한 타입명(T)을 템플릿의 템플릿 매개변수로 명시
        • template <typename T> or template <class T>
        • 타입명을 반드시 T로 해야되는 것은 아니다.
template <typename T> T max(T a, T b)
{
    return (a > b) ? a : b;
}
  • 템플릿을 사용한 max 함수의 사용
    • 템플릿 함수를 사용할 때는 자료형을 <>안에 명시
    • 단, 컴파일러가 추론 가능할 때는 생략 가능
    • 호출할 타입이 결정되면, 컴파일타임에 컴파일러가 템플릿을 기반으로 실제 함수를 생성
    • 템플릿 코드만 존재할 때는 아무 함수도 생성되지 않는다. 실제 사용이 되어야만 생성된다.
int main() {
    int a = 10;
    int b = 20;
    std::cout << max<int>(a, b) << std::endl;

    double c = 1.234;
    double d = 3.456;
    std::cout << max<double>(c, d) << std::endl;
    std::cout << max(c, d) << std::endl; // 생략 가능
}
  • 템플릿이 사용 가능한 경우
    • 템플릿 함수의 구현부에 사용된 연산이 실행 가능해야만 한다.
    • 템플릿 매개변수에 전달된 타입 인자가 클래스인 경우 해당 클래스 안에 템플릿 함수의 구현부에 사용된 연산이 정의돼 있어야 한다
class Point {
    int x;
    int y;
public: 
    Point(int x, int y): x{x}, y{y} { }
    bool operator>(const Point& p) const { // 템플릿 함수 max에 사용된 > 연산자 정의
        if((x*y) > ((p.x)*(p.y))) 
            return true;
        return false;
    }
};

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

int main() {
    Point p1(1, 1);
    Point p2(2, 4);
    max<Point>(p1, p2)
}
  • 템플릿의 다중 매개변수
    • 서로 다른 이름을 사용하여 다중 매개변수를 정의 가능
    • 매개변수가 다르다면 서로 타입이 다를 수 있음
template <typename T1, typename T2> void func(T1 a, T2 b)
{
    cout << a << " " << b;
}
int main() {
    func<int, double>(20, 20.5);
    func('A', 12.4);
}
template <typename T1, typename T2> T1 max(T1 a, T2 b)
{
    return (a > b) ? a : b;
}
int main(){
    double a = 20.6;
    int b = 3;
    std::cout << max<double, int>(a, b) << std::endl;
}
  • 템플릿의 특수화: 특정 자료형에 대해서는 템플릿 함수가 아닌 별도로 구현한 함수를 사용하도록 명시한다.
template <typename T> T max(T a, T b)
{
    return (a > b) ? a : b;
}

// std::string애 대해 템플릿 함수 max를 특수화
template <> std::string max(std::string a, std::string b) 
{
    return (a.length >= b.length) ? a : b;
}
int main() {
    std::string s1 = "abc";
    std::string s2 = "defg";
    std::cout << max<std::string>(s1, s2) << std::endl;
}
  • 함수 템플릿 다중정의
    • 함수 템플릿은 일반 함수와 마찬가지로 다중정의를 허용한다.
    • 즉, 함수 템플릿과 이름이 같은 다른 함수가 존재할 수 있다는 것이다.
void swapValues(Complex &a, Complex &b)
{
    ...
}

template <typename T> void swapValues(T &a, T &b)
{
    ...
}

template <typename T> void swapValues(T a[], T b[], int n)
{
    ...
}
  • 이름이 같은 함수가 여러 개 있을 경우에는 함수 이름과 인수들의 데이터 타입이 함수의 파라미터 타입과 정확히 일치하는 함수가 우선적으로 호출된다.
  • 그러한 함수가 없을 때는 호출한 함수와 정확하게 일치시키는 변환이 가능한 템플릿 함수가 호출된다.
int main() {
    Complex c1, c2;
    int a, b;
    int x[10], y[10];

    swapValues(c1, c2);
    swapValues(a, b);
    swqpValues(x, y, 10);
}

C++ 클래스 템플릿

  • 클래스 템플릿
    • 함수 템플릿과 유사하게 템플릿을 이용한 클래스의 구현을 지원
    • 컴파일러가 타입에 따라 적절한 클래스를 생성해준다.
  • 클래스 템플릿 정의
template <typename T>
class Item
{
private: 
    string name;
    T value;
public: 
    Item(string name, T value): name{name}, value{value} {}
    string GetName() const { return name; }
    T GetValue() const { return value; } 
}
  • 클래스 템플릿 사용
  • 함수와 같이 타입을 적지 않아도 타입을 추론하는 기능이 C++17부터 추가됨
int main() {
    Item<int> i{"A", 10}; 
    std::cout << i.GetName() << std::endl;
    std::cout << i.GetValue() < std::endl;

    Item<double> d{"D", 20.34};
    std::cout << d.GetName() << std::endl;
    std::cout << d.GetValue() < std::endl;

    Item i2{"A", 10}; // Item<int>로 추론 
    Item d2{"D", 20.34}; // Item<double>로 추론
    Item s{"s", "Hello"}; // Item<std::string>으로 추론
}
  • 클래스 템플릿의 다중 매개변수
template <typename T1, typename T2>
class MyPair {
private:
    T1 first;
    T2 second;
public: 
    MyPair(T1 val1, T2 val2)
        : first{val1}, second{val2} {}

}
int main() {
    MyPair<string, int> p1("Kim", 1);
    MyPair<int, double> p2(1, 2.43);
}
  • 클래스 템플릿의 (부분)특수화
template <typename T1, typename T2>
class MyPair {
private:
    T1 first;
    T2 second;
public: 
    MyPair(T1 val1, T2 val2)
        : first{val1}, second{val2} 
    {
        std::cout << "클래스 템플릿" << std::endl;    
    }

}

template <typename T>
class MyPair<T, double>{
private:
    T first;
    double second;
public: 
    MyPair(T1 val1, T2 val2)
        : first{val1}, second{val2} 
    {
        std::cout << "부분 특수화" << std::endl;    
    }

}
int main() {
    MyPair<int, int> p1{32, 55}; // 클래스 템플릿
    MyPair<int, double> p2{10, 20.4354}; // 부분 특수화
}
  • non-type 템플릿 매개변수
  • 템플릿 매개변수로 타입이 아닌 다른 매개변수를 사용할 수 있다.
template <typename T, int N>
class Array {
private:
    int size = N;
    T values[N];
}
int main() {
    Array<int, 5> nums;
    Array<double, 10> nums2;
}

'Language > C++' 카테고리의 다른 글

[API] STL  (3) 2025.07.31
[OOP] 연산자 다중정의  (5) 2025.07.21
[OOP] 상속과 다형성  (1) 2025.07.16
[Basic] 함수와 스코프  (0) 2025.07.16
[Basic] 연산자와 흐름 제어 구문  (6) 2025.07.16