Search
Duplicate

[C++] Template 은 무엇일까

간단소개
C++ 에서 지원하는 Template 문법에 대해서 알아봅시다.
팔만코딩경 컨트리뷰터
ContributorNotionAccount
주제 / 분류
C++
42seoul
42cursus
태그
STL
c++
Template
Scrap
9 more properties
C++ 와 객체지향의 꽃이라고 불리우는 Template
Template 가 뭔지 알아봅시다.

Template ?

템플릿은 함수 테플릿과 클래스 템플릿이 있습니다.
함수 템플릿은 함수를 만들어내는 틀, 클래스 템플릿은 클래스를 만들어내는 틀입니다.
진짜 이게 다입니다.
예제를 보면서 알아봅시다.

1) 함수 템플릿 Function Template

#include <iostream> void printFunc(int a, int b) { std::cout << a << ", " << b << std::endl; } int main(void) { printFunc(100, 999); return 0; } //OUTPUT 100, 999
C++
printFunction() 함수는 int 타입의 a와 b를 전달인자로 받아서 cout으로 출력해주는 함수입니다.
하지만 뭔가 부족합니다.
C++ 에서는 실수 말고도 다양한 다른 자료형들이 있기때문이죠.
다른 자료형도 위처럼 콤마로 나누어 출력하고 싶습니다.
어떻게 해야할까요?
템플릿이 없는 코드는 다음과 같습니다.
오버로딩으로 풀어내야 하죠.
#include <iostream> void printFunc(int a, int b) { std::cout << a << ", " << b << std::endl; } //overloading 1 void printFunc(double a, double b) { std::cout << a << ", " << b << std::endl; } //overloading 2 void printFunc(std::string a, std::string b) { std::cout << a << ", " << b << std::endl; } int main(void) { printFunc(100, 999); printFunc(3.14, 0.001592); //overloading 1 printFunc("ABCD", "EFGD"); //overloading 2 return 0; } //OUTPUT 100, 999 3.14, 0.001592 ABCD, EFGD
C++
함수 오버로딩을 통해 매개변수 ( 전달인자 )의 타입이 각각 다르고, 함수이름은 같은 세가지 함수를 정의했습니다. (int, int) , (double, double), (string, string)
함수 오버로딩은 이미 어떤 자료형을 통해 함수를 호출할지 명확하게 정의하고 있다는 전제로 만들어집니다.
BUT main 을 구현하는 사람과 printFunc 함수를 구현하는 사람이 나뉘었다고 가정해봅시다.
만일 다음과 같은 조건이 있다면 어떨까요
⇒ 그렇다면 C++에 존재하는 모든 타입에 대한 함수를 N개 정의 해야할까요??
이 문제는 함수 오버로딩으로 해결할 수 없습니다.
이런 문제를 해결하기 위해 Template 가 존재합니다.
템플릿 함수를 사용하여 Main 에서의 함수 호출 인자 타입을 보고,
템플릿 함수의 매개변수 타입을 결정하여 실제 함수인 템플릿 인스턴스 함수를 만듭니다.

문법 예시

함수 템플릿은 함수 앞에 template<typename T> 를 붙이면 됩니다.
template<typename T> {리턴타입} 함수명(T 변수명, T 변수명);
C++
#include <iostream> template<typename T> void printFunc(T a, T b) { std::cout << a << ", " << b << std::endl; } int main(void) { printFunc(100, 999); printFunc(3.14, 0.001592); printFunc("ABCD", "EFGD"); return 0; } //OUTPUT 100, 999 3.14, 0.001592 ABCD, EFGD
C++

컴파일에서..

int main(void) { printFunc(100, 999); printFunc(3.14, 0.001592); printFunc("ABCD", "EFGD"); return 0; }
C++
컴파일러의 동작은 다음과 같습니다.
1.
첫번째로 호출한 함수의 전달인자는 int, int 따라서 template의 T 가 int
2.
두번째로 호출한 함수의 전달인자는 double, double 따라서 template 의 T 는 double
3.
세번째로 호출한 함수의 전달인자는 std::string 따라서 template의 T는 std::string
컴파일러는 함수의 세가지 버전의 인스턴스를 만듭니다.

명시적 정의

다음과 같이 명시적으로 T에 들어갈 타입명을 지정할 수 있습니다.
int main(void) { printFunc(100, 999); printFunc(3.14, 0.001592); printFunc("ABCD", "EFGD"); return 0; }
C++
int main(void) { printFunc<int>(100, 999); printFunc<double>(3.14, 0.001592); printFunc<std::string>("ABCD", "EFGD"); return 0; }
C++

두가지 타입

#include <iostream> template<typename T1, typename T2> void printFunc(T1 a, T2 b) { std::cout << a << ", " << b << std::endl; } int main(void) { printFunc(100, 3.14); printFunc(999, "ThisIsString"); return 0; } //OUTPUT 100, 3.14 999, ThisisString
C++
T1, T2는 함수 템플릿의 매개변수입니다.
printFunc(100, 3.14); 에서는 T1에 int가, T2에는 double 이 들어가 생성됩니다. printFunc(999, "ThisIsString"); 에서는 T1에 int가, T2에는 std::string 이 됩니다.

2) 클래스 템플릿 Class Template

클래스 템플릿은 클래스를 만들어내는 틀 입니다.
함수템플릿과 다르지 않습니다. 단지 함수에서 클래스로 바뀌었을 뿐

클래스 템플릿 예제

템플릿 없이 Array 클래스를 만들어본다면 다음과 같습니다.
#include <iostream> class IntArray { int *buf; int size; int capacity; public: explicit IntArray( int cap = 100 ) : buf( 0 ), size( 0 ), capacity( cap ) { buf = new int[capacity]; } ~IntArray() { delete[] buf; } void Add( int data ) { buf[size++] = data; } int operator[]( int idx ) const { return buf[idx]; } int GetSize() const { return size; } }; class DoubleArray { double *buf; int size; int capacity; public: explicit DoubleArray( int cap = 100 ) : buf( 0 ), size( 0 ), capacity( cap ) { buf = new double[capacity]; } ~DoubleArray() { delete[] buf; } void Add( double data ) { buf[size++] = data; } double operator[]( int idx ) const { return buf[idx]; } int GetSize() const { return size; } }; class StringArray { std::string *buf; int size; int capacity; public: explicit StringArray( int cap = 100 ) : buf( 0 ), size( 0 ), capacity( cap ) { buf = new std::string[capacity]; } ~StringArray() { delete[] buf; } void Add( std::string data ) { buf[size++] = data; } std::string operator[]( int idx ) const { return buf[idx]; } int GetSize() const { return size; } }; void main() { IntArray iarr; // ���� Array ��ü iarr.Add( 10 ); iarr.Add( 20 ); iarr.Add( 30 ); for ( int i = 0; i < iarr.GetSize(); ++i ) std::cout << iarr[i] << " "; std::cout << std::endl; DoubleArray darr; darr.Add( 10.12 ); darr.Add( 20.12 ); darr.Add( 30.12 ); for ( int i = 0; i < darr.GetSize(); ++i ) std::cout << darr[i] << " "; std::cout << std::endl; StringArray sarr; sarr.Add( "abc" ); sarr.Add( "ABC" ); sarr.Add( "Hello!" ); for ( int i = 0; i < sarr.GetSize(); ++i ) std::cout << sarr[i] << " "; std::cout << std::endl; }
Java
IntArray, DoubleArray, StringArray 세가지의 클래스의 기능과 책임은 타입만 다를 뿐 다른것은 모두 같습니다. 이 예시는 원소 타입을 자유롭게 결정할 수 없습니다.
유연한 클래스를 만들기 위해 클래스 템플릿을 사용할 수 있습니다.

문법

template<typename T> class {Array}
C++

Array Template 예시

#include <iostream> #include <string> template <typename T> class Array { private: T *buf; int size; int capacity; public: explicit Array( int cap = 100 ) : buf( 0 ), size( 0 ), capacity( cap ) { buf = new T[capacity]; } ~Array() { delete[] buf; } void add( T data ) { buf[size++] = data; } T operator[]( int idx ) const { return buf[idx]; } int getSize() const { return size; } }; template <typename T> void printElements( T &arr ) { for ( int i = 0; i < arr.getSize(); i++ ) { std::cout << arr[i] << std::endl; } } int main( void ) { Array<int> intArr; intArr.add( 10 ); intArr.add( 20 ); intArr.add( 30 ); printElements( intArr ); std::cout << std::endl; Array<double> dblArr; dblArr.add( 3.14 ); dblArr.add( 1.59 ); dblArr.add( 2.498 ); printElements( dblArr ); std::cout << std::endl; Array<std::string> strArr; strArr.add( "abc" ); strArr.add( "EDF" ); strArr.add( "Hello World!" ); printElements( strArr ); }
C++
class Array를 T 타입으로 정의하여, main 에서 생성할때에
int, double, string 으로 각각 필요한 타입에 따라서 생성되는 것을 확인할 수 있습니다.
템플릿은 클래스의 메타 코드일 뿐이며, 컴파일러가 클래스 템플릿을 이용하여 실제 클래스(타입이 정의된 코드)를 생성합니다.

3) Template 디폴트 매개변수

c++ 에서는 default 매개변수 지정을 해주는것을 문법으로 지원합니다.
Template 에서도 default 매개변수 지정과 같이 템플릿에 아무 타입을 명시하지 않았을경우, 기본타입을 지정해 줄 수 있습니다.

문법

template <typename T = {default 타입명}, {매개변수 값}>
C++

Array 예시

#include <iostream> #include <string> template <typename T = int, int capT = 100> class Array { private: T *buf; int size; int capacity; public: explicit Array( int cap = capT ) : buf( 0 ), size( 0 ), capacity( cap ) { buf = new T[capacity]; } ... 생략 //초기화 된 capacity 확인용 int getCapacity() const { return capacity; } }; template <typename T> void printElements( T &arr ) { for ( int i = 0; i < arr.getSize(); i++ ) { std::cout << arr[i] << std::endl; } } int main( void ) { Array<> intArr; intArr.add( 10 ); intArr.add( 20 ); intArr.add( 30 ); std::cout << "capacity = " << intArr.getCapacity() << std::endl; printElements( intArr ); std::cout << std::endl; Array<double> dblArr; dblArr.add( 3.14 ); dblArr.add( 1.59 ); dblArr.add( 2.498 ); std::cout << "capacity = " << dblArr.getCapacity() << std::endl; printElements( dblArr ); std::cout << std::endl; Array<std::string, 10> strArr; strArr.add( "abc" ); strArr.add( "EDF" ); strArr.add( "Hello World!" ); std::cout << "capacity = " << strArr.getCapacity() << std::endl; printElements( strArr ); } //10 //20 //30 //capacity = 100 //3.14 //1.59 //2.498 //capacity = 100 //abc //EDF //Hello World! //capacity = 10
C++
template <typename T = int, int capT = 100>
class Array { ... }
T를 지정해주지 않았을경우, 기본값으로는 int 를 대입하게 되며, int capT 라는 변수를 선언하여 초기값을 100으로 설정해주었습니다.
Array<> intArr;
Array<double> dblArr;
Array<std::string, 10> strArr;
Array Default Template 전체 코드

3) Template 특수화

대부분의 경우 템플릿으로 제네릭 하게 사용하는 것이 해결이 되지만, 특수한 경우가 있을 수 있습니다.
예시로 확인해보겠습니다.
#include <iostream> #include <string> template <typename T> class ObjectInfo { T data; public: ObjectInfo( const T& d ) : data( d ) { } void printFunc( void ) { std::cout << "TYPE : " << typeid( data ).name() << std::endl; std::cout << "SIZE : " << sizeof( data ) << std::endl; std::cout << "VALUE : " << data << std::endl; std::cout << std::endl; } }; int main( void ) { ObjectInfo<int> d1( 10 ); d1.printFunc(); ObjectInfo<double> d2( 3.14 ); d2.printFunc(); ObjectInfo<std::string> d3( "HI!" ); d3.printFunc(); } /* TYPE : i SIZE : 4 VALUE : 10 TYPE : d SIZE : 8 VALUE : 3.14 TYPE : NSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE SIZE : 24 VALUE : HI! */
C++
ObjectInfo 클래스는 Type 을 Template 로 받아서, 해당 타입의 typeid, 크기, 실제 데이터를 출력합니다.
string 을 data , TYPE로 가지는 d3 객체를 확인해보면, typeid 함수를 통한 TYPE 값과 SIZE 값이 실제 크기와 다른것을 확인할 수 있습니다. int 와 double 은 각각 i, d 로 출력되었고, string 의 길이는 H I ! 3이어야 할 것 같지만 24로 표현됩니다.
이는 string 도 템플릿 클래스로 typedef 되어있기 때문에 그렇습니다.
따라서 이 일관성 없는 클래스를 일관성있게 만들어 주기 위해서 string 을 위한 클래스를 별도로 정의해줍니다.
template <> class ObjectInfo<std::string> { std::string data; public: ObjectInfo( const std::string& d ) : data( d ) { } void printFunc( void ) { std::cout << "TYPE : " << "string" << std::endl; std::cout << "SIZE : " << data.size() << std::endl; std::cout << "VALUE : " << data << std::endl; std::cout << std::endl; } }; /* TYPE : i SIZE : 4 VALUE : 10 TYPE : d SIZE : 8 VALUE : 3.14 TYPE : string SIZE : 3 VALUE : HI! */
C++
전체 코드
기존 코드에 위 코드를 추가해줍니다.
template<> 로 선언해주고, T 였던부분을 특수화 할 타입으로 변경해주면 됩니다.
이와같이 특수한 경우에 따로 정의해주는 것을 템플릿 특수화 라고 합니다.
이상 함수, 클래스 템플릿과 템플릿 특수화에 대해서 알아보았습니다.