C++/C++ : Study

9. Generic Programming과 템플릿 (5) - 클래스 템플릿

더블유제이플로어 2025. 7. 1. 04:44

Generic Programming using Template

◆  클래스 템플릿

   ●   함수 템플릿과 유사한, 클래스에 대한 템플릿 구현 지원
   ●   클래스의 "설계도"
   ●   컴파일러가 타입에 따라 적절한 클래스를 생성해 줌


📘 핵심 정리: 클래스 템플릿 기본 개념

🎯 클래스 템플릿이란?

타입에 관계없이 사용할 수 있도록 클래스의 설계도(Template) 를 만드는 것
template <typename T>
class MyBox {
private:
    T data;
public:
    MyBox(T value) : data(value) {}
    void print() {
        std::cout << "Value: " << data << std::endl;
    }
};
  • T는 자료형을 나타내는 템플릿 인자
  • 클래스 전체에서 T는 타입 자리에 사용됨 (멤버 변수, 멤버 함수 등)
  • 함수 템플릿과 문법은 유사하지만, 클래스 전체를 일반화

🎯 컴파일러 역할

  • 컴파일러는 T 자리에 지정된 타입을 넣어서 구체적인 클래스를 자동으로 생성해줌
  • 예: Box<int>, Box<string> 이런 식으로 타입별 클래스가 컴파일 타임에 생성됨

🎯 중요 이유

  • STL(Standard Template Library) 대부분이 클래스 템플릿으로 구성되어 있음
  • 예: vector<T>, pair<T1, T2>, map<K, V> 등 전부 클래스 템플릿

 정리

핵심 포인트  요약 
클래스 템플릿 정의 함수 템플릿과 문법 유사, template <typename T> 사용
클래스 템플릿 목적 다양한 타입에 대해 하나의 클래스 구조를 재사용
STL과 연결 vector, map 등 STL 컨테이너는 전부 클래스 템플릿

◆  클래스 템플릿, item 클래스 예제

class Item
{
private:
    string name;
    int value;
public:
    Item(string name, int value)
        :name{ name }, value{ value }
    { }
    string getName() const { return name; }
    int getValue() const { return value; }
};

◆  클래스 템플릿의 정의

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

◆  클래스 템플릿의 사용

Item<int> item1{ "Kim", 1 };
Item<double> item2{ "Lee", 10.5 };
Item<string> item3{ "Park", "Hello" };

📘 클래스 템플릿: Item 클래스 예제 정리

✅클래스 템플릿 전 기본 구조

클래스 Item을 만들고 멤버 변수로는 string name과 int value를 가진다.
생성자에서는 이름과 value를 받아 저장하고,
getName(), getValue() 함수는 const로 정의되어 각각 값을 반환해준다.

class Item {
private:
    string name;
    int value;
public:
    Item(string name, int value)
        : name{ name }, value{ value } { }

    string getName() const { return name; }
    int getValue() const { return value; }
};
Item i{ "A", 10 };
std::cout << i.getName() << std::endl;   // A
std::cout << i.getValue() << std::endl;  // 10

정상적으로 잘 동작한다.

✅템플릿화

이제 value가 int로 고정되어 있어서,
value를 int, double, string, char 등
타입에 관계없이 사용할 수 있도록 템플릿화할 것이다.

  1. value를 T 타입으로 바꾸고
  2. 인자로 받는 생성자 인자도 T 타입으로 변경
  3. getValue() 함수 반환형도 T로 바꿔준다
  4. 마지막으로 template <typename T>를 클래스 위에 선언해준다
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; }
};

✅클래스 템플릿 사용

템플릿 클래스는 객체를 생성할 때 타입을 명시해주어야 한다.
무엇의 타입? 바로 value의 타입이다.

Item<int> i{ "A", 10 };
std::cout << i.getName() << std::endl;   // A
std::cout << i.getValue() << std::endl;  // 10

Item<double> d{ "B", 1.345 };
std::cout << d.getName() << std::endl;   // B
std::cout << d.getValue() << std::endl;  // 1.345

잘 동작하는 것을 알 수 있다.

✅정리

우리는 단 하나의 설계도만 정의했지만,
Item<int> i를 보면 컴파일러가 T에 int를 대입해
하나의 클래스를 만들어준다.

Item<double> d가 등장하면 T에 double을 넣어서
또 다른 클래스를 만들어준다.

Item이라는 이름의 클래스가 여러 개 생기는 건데 괜찮은가?

그렇다. 괜찮다.
템플릿에서는 타입까지 포함해서 이름이 완성된다고 생각하면 된다.

즉,

  • Item<int>와 Item<double>는 서로 다른 타입이자, 서로 다른 클래스
  • Item<int>, Item<double>는 완전히 별개의 템플릿 인스턴스

❓ 왜 Item<int>와 Item<double>은 완전히 다른 타입인가?

클래스 템플릿은 단순한 "클래스 정의"가 아니라 클래스 설계도(template)이다.
Item<int>와 Item<double>는 이 설계도를 바탕으로
서로 다른 타입 T를 넣어 컴파일 타임에 생성된 두 개의 완전한 클래스이다.

예를 들어, 아래와 같이 컴파일러는 내부적으로 두 개의 클래스를 만들어낸다:

// Item<int>의 경우
class Item_int {
private:
    string name;
    int value;
public:
    Item_int(string name, int value) : name(name), value(value) {}
    string getName() const { return name; }
    int getValue() const { return value; }
};

// Item<double>의 경우
class Item_double {
private:
    string name;
    double value;
public:
    Item_double(string name, double value) : name(name), value(value) {}
    string getName() const { return name; }
    double getValue() const { return value; }
};

✅ 즉, Item<int>와 Item<double>는 컴파일러 입장에서 완전히 다른 클래스로 취급됨.
서로 대입도 안 되고, 함수 인자도 호환되지 않음.

Item<int> i("A", 10);
Item<double> d("B", 3.14);

i = d;         // ❌ 다른 타입이라 오류 발생
func(i);       // void func(Item<int>) 따로 정의해야 함
func(d);       // void func(Item<double>) 따로 정의해야 함

📌 이처럼 C++ 템플릿은 타입마다 별도의 정의를 생성하기 때문에
Item<int>와 Item<double>는 다른 클래스이자, 다른 타입이라는 점을 꼭 기억해야 한다.


고급 Part이다.

◆  클래스 템플릿의 사용 

   ●   함수처럼 타입을 적지 않아도 타입을 추론하는 기능이 C++ 17부터 추가됨

Item item1{ "Kim", 1 }; // Item<int> 추론
Item item2{ "Lee", 10.5 }; // Item<double> 추론
Item item3{ "Park", "Hello" }; //Item<string> 추론

   ●   VS2017에서는 프로젝트 속성 → C/C++ 언어 C++ 언어 표준을 C++17로 바꾸어 주어야 가능.


❓ 머신비전 업계에서 C++ 표준 버전은 어느 정도까지 고려해야 하나?

그렇다, 고려해야 한다.

머신비전 업계에서는 보통 아래와 같은 환경이 많다:

  • 장비 납품용 / 실시간 시스템용 소프트웨어:
    대부분 Windows 기반 Visual Studio 2017, 2019에서 빌드되며
    C++14 또는 C++17이 주로 사용됨
  • 산업용 라이브러리 사용 (예: Halcon, Matrox MIL, OpenCV):
    이들 라이브러리의 빌드 요구사항이 C++11~C++17 수준에 맞춰져 있음

💡 즉, 최신 기능(C++20, C++23)을 쓰고 싶어도
실제 장비 SDK나 라이브러리 호환성 때문에
C++17까지가 안정적 선택이 되는 경우가 많음.

❓ 머신비전 업계에서는 C++ 몇 버전을 가장 많이 사용하는가?

✅ 실무 기준으로 정리하면:

C++ 버전 업계 사용 여부  비고 
C++11 ✅ 여전히 많이 사용됨 오래된 SDK 호환
C++14 ✅ 가장 기본값 Visual Studio 2017 기본값
C++17 ✅ 점점 증가 중 최신 SDK/라이브러리 대응
C++20 ❌ 제한적 사용 호환성, 컴파일 속도 문제
C++23 ❌ 거의 안 씀 테스트 환경 또는 R&D용

➡ 머신비전 포트폴리오를 만들거나 실제 장비용 코드 짤 때는
C++14 또는 17에서 개발하고, 추후 최신 기능을 실험할 땐
별도 프로젝트에서 C++20 이상을 고려하는 게 좋음.

❓ GitHub에 올릴 포트폴리오용 C++ 프로젝트는 몇 버전으로 개발하는 게 좋을까?

✅ 추천은 C++17 기준으로 작성 + 주석으로 표기하는 것!

💡 이유:

  1. C++14 이하: 너무 구식으로 보임. 현대적인 감각 안 느껴짐
  2. C++20 이상: 모던하고 멋져 보이지만,
    ❌ 업계 호환성 떨어지고
    ❌ 일부 기업에서는 “지나치게 실험적인 개발자”처럼 보일 수 있음

📌 그래서 이렇게 정리하면 좋아:

// C++17 표준 기준
// g++ -std=c++17 -o program main.cpp

또는 CMake 사용 시:

set(CMAKE_CXX_STANDARD 17)

🔹 GitHub에 올리는 코드라면:

  • 읽기 쉬운 구조
  • 명확한 버전 주석
  • 업계와 호환성 유지

이 세 가지가 중요하고, 그 기준엔 C++17이 가장 안전하고 세련됨.


◆  클래스 템플릿의 다중 매개변수 

   ●   선언과 사용 

template <typename T1, typename T2>
class MyPair {
private:
    T1 first;
    T2 second;
public:
    MyPair(T1 val1, T2 val2)
        : first{ val1 }, second{val2}
    { }
};

MyPair<string, int> p1{ "Kim", 1 };
MyPair<int, double> p2{ 123,45.6 };

📘 클래스 템플릿: 다중 매개변수 사용

🎯 마찬가지로 클래스 템플릿에서도 함수 템플릿처럼
다중 매개변수를 사용할 수 있다.

기존에는 템플릿 인자가 T 하나였지만,
이제는 두 개 이상의 타입 인자를 줄 수 있다.

📄 예제: 다중 매개변수 클래스 템플릿 정의

template <typename T1, typename T2>
class Item {
private:
    T1 name;
    T2 value;
public:
    Item(T1 name, T2 value)
        : name{ name }, value{ value } { }

    T1 getName() const { return name; }
    T2 getValue() const { return value; }
};
  • name의 타입도 일반화하기 위해 T1으로 정의
  • value는 기존처럼 T2로 정의
  • 생성자와 반환형 모두 해당 타입을 그대로 사용

🎯 기존 코드

Item<int> item1{ "A", 10 };

➡ 이제는 타입 인자가 2개이므로, 위 코드는 오류가 발생한다.
두 타입을 모두 명시해주어야 한다.

📄 예제: 다중 매개변수 클래스 템플릿 사용

Item<std::string, int> item1{ "A", 10 };      // name: string, value: int
Item<int, double> item2{ 10, 1.345 };         // name: int, value: double
  • 템플릿 인자가 2개이므로 <T1, T2> 형식으로 타입을 모두 명시해야 한다
  • 타입이 다르기 때문에 다양한 조합의 클래스를 만들 수 있다

🎯 이렇게 클래스 템플릿도 함수 템플릿과 마찬가지로
다중 타입 인자를 사용하여 다양한 타입 조합의 설계도를 만들 수 있다.

컴파일러는 호출 시점에서 T1, T2에 맞는
별도의 클래스 정의를 생성해준다.


◆  클래스 템플릿의 (부분) 특수화

   ●   class classname <type 명시>

template <typename T>
class Item
{
private:
    string name;
    T value;
public:
    ...
};

template <typename T>
class Item<T, double>
{
private:
    T key;
    double value;
public:
    ...
};
value 가 double 형일 때 사용할 클래스 템플릿의 특수화

📘 클래스 템플릿의 특수화

🎯 클래스 템플릿은 함수 템플릿과 마찬가지로 특수화(specialization)가 가능하다.
그 중 부분 특수화는 모든 타입 인자가 아니라 일부만 특수한 타입으로 고정하는 것이다.

예를 들어, 아래의 일반 클래스 템플릿이 있다고 하자:

template <typename T1, typename T2>
class Item {
private:
    T1 name;
    T2 value;
public:
    Item(T1 name, T2 value)
        : name(name), value(value) {}

    T1 getName() const { return name; }
    T2 getValue() const { return value; }
};

🎯 두 번째 인자 T2가 double일 때만 특수한 동작을 하게 하고 싶다면?

template <typename T1>
class Item<T1, double> {
private:
    T1 name;
    double value;
public:
    Item(T1 name, double value)
        : name(name), value(value) {}

    T1 getName() const { return name; }

    // 특수화된 동작: 값을 2배로 반환
    double getValue() const { return 2 * value; }
};
  • 여기서 T1은 여전히 템플릿 인자로 유지되고,
  • T2는 double로 고정되었기 때문에
    템플릿 선언은 template<typename T1>만 하면 된다.
  • Item<T1, double>은 부분 특수화된 버전이다.

📄 사용 예

Item<std::string, int> a("Apple", 100);
std::cout << a.getValue() << std::endl; // 100 (일반 템플릿)

Item<std::string, double> b("Banana", 1.23);
std::cout << b.getValue() << std::endl; // 2.46 (특수화된 템플릿)

❓ 클래스 템플릿 부분 특수화를 선언할 때 "인수가 너무 많다"는 오류가 발생하는 이유는?

✅ 해당 오류는 일반 템플릿 정의 없이 특수화만 먼저 작성했거나,
혹은 부분 특수화 구문을 잘못 작성했기 때문에 발생한다.

정확한 구조는 다음과 같아야 함:

// 일반 템플릿 정의 (기본 버전)
template <typename T1, typename T2>
class Item {
    // ...
};

// 부분 특수화 (T2가 double인 경우)
template <typename T1>
class Item<T1, double> {
    // ...
};

🚫 주의:

  • 일반 템플릿 정의가 먼저 있어야 함 (위에 있어야 함)
  • class Item<T1, double>은 템플릿 인자 2개짜리 구조를 고정하는 방식이므로,
    이 전에 Item<T1, T2>라는 일반 정의가 없으면 "인수가 너무 많다" 오류 발생

❓ 클래스 템플릿 특수화가 정확히 뭐고, 일반/부분 특수화는 어떻게 구분되나?

✅ 클래스 템플릿의 특수화는 특정 타입 조합에 대해 동작을 바꾸는 것이다.
이를 통해 같은 이름의 클래스라도, 타입에 따라 전혀 다르게 작동할 수 있다.

구분  정의  예시 
일반 템플릿 모든 타입에 대해 동일하게 작동하는 기본 클래스 template<typename T1, typename T2> class Item
전체 특수화 두 타입 모두 고정 template<> class Item<std::string, int>
부분 특수화 한 쪽 타입만 고정, 나머지는 유지 template<typename T1> class Item<T1, double>

💡 부분 특수화는 실무에서 가장 유용하게 쓰인다.
특정 타입(예: double)만 예외 처리하듯 다르게 동작시킬 수 있기 때문이다.

 


https://youtu.be/NJONOHTf6iY?si=f_CxT4TQI1G9GdZP