클래스 템플릿
클래스 템플릿의 개념
클래스 템플릿은 C++에서 제공하는 강력한 기능으로, 타입에 독립적인 클래스를 정의할 수 있게 해줍니다.
이를 통해 다양한 데이터 타입에 대해 동작하는 일반화된 클래스를 작성할 수 있습니다.
클래스 템플릿의 기본 구조
template <typename T>
class ClassName {
// 클래스 멤버 선언 및 정의
};
template
: 템플릿 선언을 시작하는 키워드입니다.typename T
: 템플릿 매개변수를 선언합니다.class T
로 쓸 수도 있습니다.T
: 템플릿 매개변수로, 실제 사용 시 구체적인 타입으로 대체됩니다.
간단한 클래스 템플릿 예제 : Box 클래스
다음은 어떤 타입의 값이든 저장할 수 있는 Box
클래스 템플릿의 예입니다.
#include <iostream>
template <typename T>
class Box {
private:
T content;
public:
Box(T item) : content(item) {}
T getContent() const {
return content;
}
void setContent(T item) {
content = item;
}
};
int main() {
Box<int> intBox(5);
std::cout << "Int Box contains: " << intBox.getContent() << std::endl;
Box<std::string> stringBox("Hello, World!");
std::cout << "String Box contains: " << stringBox.getContent() << std::endl;
return 0;
}
이 예제에서 Box
클래스 템플릿은 정수, 문자열 등 다양한 타입의 값을 저장할 수 있습니다.
템플릿 인스턴스화
클래스 템플릿을 사용할 때, 구체적인 타입을 지정하여 템플릿을 인스턴스화합니다.
Box<int> intBox(5); // T를 int로 인스턴스화
Box<std::string> stringBox("Hello"); // T를 std::string으로 인스턴스화
컴파일러는 이러한 인스턴스화를 통해 실제 클래스 코드를 생성합니다.
멤버 함수 정의
클래스 템플릿의 멤버 함수는 클래스 내부 또는 외부에서 정의할 수 있습니다.
클래스 내부 정의
template <typename T>
class MyClass {
public:
void myFunction() {
// 함수 구현
}
};
클래스 외부 정의
template <typename T>
class MyClass {
public:
void myFunction();
};
template <typename T>
void MyClass<T>::myFunction() {
// 함수 구현
}
다중 템플릿 매개변수
클래스 템플릿은 여러 개의 템플릿 매개변수를 가질 수 있습니다.
#include <iostream>
template <typename T, typename U>
class Pair {
private:
T first;
U second;
public:
Pair(const T& f, const U& s) : first(f), second(s) {}
T getFirst() const { return first; }
U getSecond() const { return second; }
void print() const {
std::cout << "(" << first << ", " << second << ")" << std::endl;
}
};
int main() {
Pair<int, std::string> pair1(1, "One");
pair1.print();
Pair<double, char> pair2(3.14, 'π');
pair2.print();
return 0;
}
템플릿 특수화
특정 타입에 대해 다른 구현을 제공하고 싶을 때 템플릿 특수화를 사용할 수 있습니다.
#include <iostream>
#include <cstring>
// 일반 템플릿
template <typename T>
class Storage {
private:
T value;
public:
Storage(T val) : value(val) {}
void print() const {
std::cout << "Value: " << value << std::endl;
}
};
// char* 타입에 대한 특수화
template <>
class Storage<char*> {
private:
char* value;
public:
Storage(const char* val) {
value = new char[strlen(val) + 1];
strcpy(value, val);
}
~Storage() {
delete[] value;
}
void print() const {
std::cout << "Value (char*): " << value << std::endl;
}
};
int main() {
Storage<int> intStorage(5);
intStorage.print();
Storage<char*> charStorage("Hello, World!");
charStorage.print();
return 0;
}
클래스 템플릿과 상속
클래스 템플릿은 다른 클래스 템플릿을 상속받을 수 있습니다.
template <typename T>
class Base {
protected:
T value;
public:
Base(T val) : value(val) {}
virtual void print() const {
std::cout << "Base value: " << value << std::endl;
}
};
template <typename T>
class Derived : public Base<T> {
public:
Derived(T val) : Base<T>(val) {}
void print() const override {
std::cout << "Derived value: " << this->value << std::endl;
}
};
int main() {
Base<int>* basePtr = new Derived<int>(5);
basePtr->print(); // 출력: Derived value: 5
delete basePtr;
return 0;
}
클래스 템플릿의 장단점
장점
- 코드 재사용성 증가
- 타입 안전성 보장
- 일반화된 컨테이너 및 알고리즘 구현 가능
단점
- 컴파일 시간 증가
- 오류 메시지가 복잡해질 수 있음
- 코드 크기 증가 가능성 (코드 블로트)
실습 : 제네릭 스택 구현
다음 요구사항을 만족하는 스택 클래스 템플릿을 구현해보세요.
Stack
클래스 템플릿 작성push
,pop
,top
,empty
,size
메서드 구현- 스택이 비어있을 때
pop
이나top
을 호출하면 예외 처리
#include <iostream>
#include <vector>
#include <stdexcept>
template <typename T>
class Stack {
private:
std::vector<T> elements;
public:
void push(const T& elem) {
elements.push_back(elem);
}
T pop() {
if (empty()) {
throw std::out_of_range("Stack is empty");
}
T top = elements.back();
elements.pop_back();
return top;
}
T& top() {
if (empty()) {
throw std::out_of_range("Stack is empty");
}
return elements.back();
}
bool empty() const {
return elements.empty();
}
size_t size() const {
return elements.size();
}
};
int main() {
try {
Stack<int> intStack;
intStack.push(5);
intStack.push(10);
intStack.push(15);
std::cout << "Stack size: " << intStack.size() << std::endl;
std::cout << "Top element: " << intStack.top() << std::endl;
while (!intStack.empty()) {
std::cout << "Popped: " << intStack.pop() << std::endl;
}
// 이 줄은 예외를 발생시킬 것입니다
intStack.pop();
}
catch (const std::exception& e) {
std::cerr << "Exception caught: " << e.what() << std::endl;
}
return 0;
}
연습 문제
Pair
클래스 템플릿을 확장하여 두 값을 교환하는swap
멤버 함수를 추가하세요.Array
클래스 템플릿을 구현하세요. 이 클래스는 고정 크기의 배열을 나타내며,operator[]
를 오버로딩하여 요소에 접근할 수 있어야 합니다.Queue
클래스 템플릿을 구현하세요.enqueue
,dequeue
,front
,empty
,size
메서드를 포함해야 합니다.
참고자료
- "C++ Templates : The Complete Guide" by David Vandevoorde and Nicolai M. Josuttis
- "Effective C++" by Scott Meyers (항목 42 : typename의 두 가지 의미를 제대로 파악하자)
- "Modern C++ Design" by Andrei Alexandrescu
- C++ Reference : Class templates
- C++ Core Guidelines : T : Templates and generic programming