9. Generic Programming과 템플릿 (5) - 클래스 템플릿
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 등
타입에 관계없이 사용할 수 있도록 템플릿화할 것이다.
- value를 T 타입으로 바꾸고
- 인자로 받는 생성자 인자도 T 타입으로 변경
- getValue() 함수 반환형도 T로 바꿔준다
- 마지막으로 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 기준으로 작성 + 주석으로 표기하는 것!
💡 이유:
- C++14 이하: 너무 구식으로 보임. 현대적인 감각 안 느껴짐
- 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