3냥 집사이면서 게임 개발자입니다.
[전문가를 위한 C++] 클래스 템플릿 본문
기존의 함수를 작성하는 것을 매개변수화한다고 표현한다.
템플릿은 매개변수화 개념을 더욱 발전시켜 값 뿐만 아니라 타입에 대해서도 매개변수화 한다.
템플릿을 이용하면 주어진 값 뿐만 아니라 그 값의 타입에 대해서도 독립적인 코드를 작성할 수 있다.
예를 들어 int, Car, SpreadsheetCell 과 같은 각각의 타입마다 따로 정의하지 않고, 스택 클래스 하나에 모든 타입에 적용할 수 있게 만들 수 있다.
템플릿이 제공하는 기능이 정말 뛰어나다는 점과 C++의 템플릿 문법이 상당히 복잡해서 템플릿을 집중적으로 학습자고자한다.
C++에서 제공하는 템플릿 기능을 주로 표준 라이브러리를 사용하는 관점에서 작성하려한다.
클래스 템플릿
멤버 변수 타입, 메서드의 매개 변수 또는 리턴 타입을 매개변수로 받아서 클래스를 만든다. 클래스 템플릿은 주로 객체를 저장하는 컨테이너나 데이터 구조에서 많이 사용한다.
컴파일러에서 템플릿을 처리하는 방식
컴파일러는 템플릿 메서드를 정의하는 코드를 발견하면 문법 검사만 하고 템플릿 코드를 실제로 컴파일 하지 않는다.
컴파일러가 템플릿을 인스턴스화 하는 코드를 발견하면 클래스 템플릿의 매개변수 T 에 자료형을 대입해서 해당 자료형 버전의 클래스를 생성한다.
템플릿 기능이 없을 때는 원소의 타입마다 일일이 클래스를 따로 정의했어야 할 작업을 컴파일러가 대신 해주는 것이다.
특정 타입의 인스턴스를 정의하는 코드가 작성되어 있다면, 컴파일러는 단순 반복적인 '복사해서 붙여넣기'와 '단어 바꾸기' 작업을 자동화한 것에 불과하다.
클래스 템플릿의 인스턴스화 제한하기
템플릿 코드 정의문이 지난 마지막에 template class Grid<int> 와 같이 인스턴스화를 허용할 타입을 명시적으로 나열해준다.
이렇게 작성한 타입만 빌드를 진행한다.
그렇다면 자료형 타입과 데이터를 저장하는 클래스 템플릿을 만들어보자.
tuple 과 함수 템플릿 get() 정의했습니다.
데이터를 이용해 타입을 추론하는 decltype 키워드를 이용해 생성한 인스턴스의 인덱스 값을 다시 뽑아내도록 구현했습니다.
make_variants 를 이용해 생성한 클래스 템플릿 인스턴스가 잘 생성됐는지 증명하는 코드입니다.
위 코드에는 해당되지않지만, 주의해야 할 점은 auto 키워드와 decltype 키워드다.
auto 키워드는 표현식의 타입을 추론하면 레퍼런스와 const 지정자가 사라진다.
반면 decltype은 이를 제거하지 않는다.
일반 함수를 예로 들자.
const std::string message = "Test";
const std::string& getString()
{
return message;
}
// 레퍼런스와 const 지정자가 사라져 복제 연산이 발생한다.
auto s1 = getString();
// 이와 같이 명시적으로 지정해야한다.
const auto& s2 = getString();
//또 다른 방법인 decltype 을 사용한다.
decltype(getString()) s3 = getString();
// getString()을 두번이나 사용해 코드 중복이 발생한다.
// 더 복잡한 형태의 표현이라면 코드가 상당히 지저분해진다.
// 이 문제를 아래 형태의 코드로 해결할 수 있다.
// C++14 이후부터 리턴 타입 추론 기능과 decltype(auto)가 지원된다.
decltype(auto) s4 = getString();
// s4 역시 const string& 타입이다.
// 위 클래스 템플릿 코드에서는 대체 함수 구문(후행 리턴 타입)으로 해결했다.
// 예시 코드
template<typename T1, typename T2>
auto add(const T1& t1, const T2& t2) -> decltype(t1+t2)
{
return t1+t2;
}
템플릿 특수화
특정한 타입에 대해 클래스 템플릿을 다른 방식으로 구현하게 만들 수 있다.
예를 들어 기존의 클래스 템플릿은 얕은 복사를 사용하는데, 깊은 복사가 필요한 const char* 타입의 클래스를 인스턴스화 해야한다면, const char*에 대해서만 다르게 처리하도록 구현해야 한다.
기존의 클래스와 같은 방식으로 구현하되, typename T를 사용하지 않고, class Grid<const char*> 와 같이 명시적으로 지정한다.
이렇게 할거면 뭐하러 템플릿으로 취급할까 의구심이 들 때가 있다.
template<>
class Grid<const char*>
이렇게 작성하면 컴파일러는 이 클래스가 const char*에 특수화한 Grid라고 판단한다.
템플릿을 특수화 하는 것은 상속(파생)의 개념과는 다르다. 특수화할 때는 클래스 전체를 완전히 새로 구현해야 한다.
사실 원본 클래스와 전혀 다른 형태로 구현해도 된다. 물론 템플릿 특수화 기능을 본래 목적과 다르게 남용하는 것이기 때문에 특별한 이유가 없다면 이렇게 작성하는 것을 권장하지 않는다.
'CPPTMP' 카테고리의 다른 글
[전문가를 위한 C++] 메서드 템플릿 (0) | 2024.11.28 |
---|