항목 41 : 템플릿의 특성
템플릿은 다음과 같은 두가지의 추가적인 특성을 가지는 문법이다.
암시적 인터페이스
기존 문법에서는 특정 타입의 연산자나 멤버에 접근할 때 명시적으로 특정 타입의 인터페이스에 접근한다. 하지만 템플릿에서는 타입 매개변수의 객체에 대해 암시적 인터페이스를 통해 멤버에 접근한다. 타입 매개변수에 입력되는 타입들은 해당 템플릿에서 상용한 암시적 인터페이스들에 대해 모두 유효해야한다. 그렇지 않다면 템플릿 인스턴스를 만드는 컴파일 타임에 에러가 발생한다.
컴파일 타임 다형성
기존 문법에서는 다형성을 사용할 때 런타임에 해당 레퍼런스에 들어있는 실제 객체가 무엇인지 인식하여 호출될 함수가 정해졌었다. 그에 비해 템플릿의 타입 매개변수를 활용한 다형성은 템플릿 인스턴스를 만들 때 해당 타입의 함수를 호출한다는 것을 인식하여 바로 확정된다. 이를 컴파일 타임 다형성이라고 한다.
항목 42 : typename
템플릿에서 typename 키워드는 다음 두가지 용도로 사용된다.
1. 템플릿 선언문에서 template<typename T>와 같이 타입 매개변수를 나타날때 사용된다. 이때 typename을 class로 변경해도 완전히 똑같이 작동한다.
2. 탬플릿 클래스나 함수에서 중첩 의존 이름이 타입임을 명시해줄때 사용한다.
여기서 중첩 의존 이름은 탬플릿 매개변수 그 자체 또는 이에 영향받아 바뀔 수 있는 클래스안의 이름을 나타낸다. 즉
T::const_iterator 등이 해당된다. 이때 컴파일러는 T::const_iterator를 해석할때 T타입 안의 const_iterator로 해석하는데 여기서 T타입의 전역 변수인지, typedef등에 의한 타입인지를 알 수 없기에 기본적으로 타입이 아닌것으로 해석한다.
따라서 만약 이를 타입으로 사용할 것이라면 typename T::const_iterator로 명시해야한다.
이때 T::const_iterator가 기본적으로 타입으로 해석되는 예외적인 상황이 두개있는데 하나는 상속받을 부모 타입을 지정할때와 두번째는 생성자의 초기화리스트에서 이러한 부모의 생성자를 나타낼때 이다. 이때는 typename을 붙여서는 안된다.
항목 43 : 템플릿에 의한 부모의 이름에 접근하기
만약 어떤 클래스 템플릿이 자신의 템플릿 매개변수로 만들어진 부모 타입을 상속받는다고 하자. 이때 이러한 부모의 멤버들에는 바로접근 할 수 없다. 이는 바로 컴파일 시점에 부모가 정확히 어떤 인터페이스를 가지고 있을지 모르기 때문이다. (parent<T> 타입을 상속받는다고 해도 특수화등을 고려하면 알 수가 없다.)
따라서 이에 접근하기 위해선 다음 세가지 방법중 하나를 써야한다. 부모 타입이 parent<T>라고 가정한다.
1. this->부모의 멤버 로 접근
2. using parent<T>::부모의 멤버 를 선언해두고 그냥 사용
3. parent<T>::부모의 멤버 로 접근
이때 3번은 가상함수의 경우 정상적으로 처리되지 않기에 사용을 추천하지 않는다.
항목 44 : 코드 비대화 방지
템플릿의 경우 인스턴스가 생성될 때마다 코드가 복사되기에 코드 비대화를 조심해야한다. 따라서 코드 비대화를 최대한 줄일 수 있는 디자인을 하도록 노력해야한다.
만약 우리가 원소 타입과 크기 두 매개변수를 받는 정방행렬 클래스 템플릿을 만들었다고 하자. 만약 이 템플릿이 .역행렬을 계산하는 invert 멤버 함수를 가지고 있다면 각 타입의 각 크기에 대한 객체를 만들때마다 invert함수가 복사될 것이다. 이를 막기 위해선 비멤버 함수 템플릿으로 절차지향으로 invert함수를 만들면 한 원소 타입의 행렬마다 invert가 하나씩만 생겨 코드 비대화를 막을 수 있다.
만약 객체 지향으로 이를 구현하고 싶다면 원소 타입하나만 매개변수로 받는 부모 클래스 템플릿을 만들고 여기에 invert함수를 만든다. 그리고 멤버 변수로 원소 타입의 포인터 포인터를 가진다. invert함수는 size를 함수 매개변수로 받는다. 그후 실제 행렬 템플릿에서는 이를 자신의 타입으로 상속받고 생성자에서 자신의 실제 행렬의 원소 포인터 포인터를 부모 생성자에 넘겨 부모도 이 포인터를 가지게 한뒤 자신의 invert를 호출시 부모의 invert에 자신의 사이즈를 넘겨주며 호출하면 된다. 이렇게 하면 코드 비대화를 줄이면서도 객체지향을 유지할 수 있다.
타입 매개변수
이러한 코드 비대화는 타입 매개변수에 대해서도 마찬가지로 생각할 수 있다. 만약 어떤 템플릿이 어떤 부분에서 포인터 타입 매개변수를 처리한다고하자. 이때 이러한 포인터에 대한 처리가 단순 포인터 처리뿐이라면 void*로 통일시켜 처리해도 동일한 효과가 나는 상황도 있을 수 있다. 이럴때 void*로 구현된 로직을 따로 빼서 이를 호출하는 식으로 그 부분을 구현한다면 코드비대화를 줄일 수 있다.
참고서적
Effective C++
'프로그래밍 언어 > Effective C++' 카테고리의 다른 글
[Effective C++]항목 49~52: new와 delete (0) | 2023.06.11 |
---|---|
[Effective C++]항목 45~48: 템플릿 2 (1) | 2023.06.10 |
[Effective C++]항목 38~40: 객체 지향 설계 3 (0) | 2023.06.01 |
[Effective C++]항목 35~37: 객체 지향 설계 2 (0) | 2023.05.15 |
[Effective C++]항목 32~34: 객체 지향 설계 1 (0) | 2023.05.15 |