43.템플릿으로 만들어진 기본 클래스 안의 이름에 접근하는 방법을 알아 두자
템플릿 클래스를 기본 클래스로 파생 시키는 클래스를 구현 할 상황이 생긴다.
class CompanyA
{
public:
void SendClearText(const string& msg);
void SendEncrypted(const string& msg);
};
class CompanyB
{
void SendClearText(const string& msg);
void SendEncrypted(const string& msg);
};
class MsgInfo {};
template<typename Company>
class MsgSender
{
public:
void SendClear(const MsgInfo& info)
{
string msg;
Company c;
c.SendClearText(msg); //암시적 인터페이스
}
void SendSecret(const MsgInfo& info);
};
template<typename Company>
class LoggingMsgSender : public MsgSender<Company> //템플릿 클래스의 파생 클래스
{
public:
void SendClearMsg(const MsgInfo& info)
{
//메세지 전송 전 정보를 로그에 기록
SendClear(info);
//메세지 전송 후 정보를 로그에 기록
}
};
기본 클래스의 비가상 함수를 오버라이드 하지 않기 위해 SendClearMsg로 이름을 바꾸었지만(36장 참고), 이 코드는 문제가 존재한다.
파생 클래스인 LoggingMsgSender에서 SendClearMsg 함수 안에 SenClear 함수는 존재하지 않는 취급을 받는다. 기본 클래스에 있음에도 컴파일러는 기본 클래스를 들여다보려고 하지 않는다.
결론적으로 컴파일 되지 않는다.
완전 템플릿 특수화(total template specialization)
완전 템플릿 특수화는 일반형 템플릿을 템플릿 매개변수가 특정 변수 일 때 사용할 수 있도록 특수화하는 것이다.
class CompanyC
{
public:
void SendEncrypted(const string& msg);
};
위 코드에서 CompanyC는 MsgSender 템플릿을 사용하기엔 문제가 있다. MsgSender 템플릿 클래스의 SendClear 함수안의 SendClearText 함수가 없기 때문이다.
MsgSender 템플릿을 사용할 때 템플릿 매개변수가 CompanyC일 경우엔 SendClearText 함수가 사용하지 않게 전용 함수를 지정해 주면 좋을 것 같다. 이때 사용하는 것이 완전 템플릿 특수화이다.
template<> //완전 템플릿 특수화 선언
class MsgSender<CompanyC> //템플릿 매개변수에 CompanyC일 경우
{
public:
void SendSecret(const MsgInfo& info);
};
template<>처럼 괄호 안에 아무것도 없는 template의 뜻은 템플릿도 아니고 클래스도 아니다 라는 것이다.
위 코드는 MsgSender 템플릿을 템플릿 매개변수가 CompanyC일 때 쓸 수 있도록 특수화한 버전이다.
즉, CompanyC외 다른 매개변수로는 사용할 수 없다.
MsgSender 템플릿의 파생 클래스인 LogginMsgSender 클래스는 어떤가?
CompanyC가 템플릿 매개변수로 들어왔을 때 SendClear 함수가 없으므로 이 역시 사용할 수 없다.
이런 특수화 버전에서 제공하는 인터페이스가 원래의 일반형 템플릿과 꼭 같으리란 법을 C++가 인식하기 때문에 C++ 컴파일러는 템플릿으로 만들어진 기본 클래스를 뒤져서 상속된 이름을 찾는 것을 거부한다.
C++의 템플릿화된 기본 클래스는 멋대로 찾아보지 않을꺼야 라는 동작이 발현되지 않도록 하는 방법은 세 가지가 있다.
1.기본 클래스 함수에 대한 호출문 앞에 this->를 사용한다.
template<typename Company>
class LoggingMsgSender : public MsgSender<Company>
{
public:
void SendClearMsg(const MsgInfo& info)
{
//메세지 전송 전 정보를 로그에 기록
this->SendClear(info); //this-> 선언
//메세지 전송 후 정보를 로그에 기록
}
};
2. using 선언을 사용한다.
template<typename Company>
class LoggingMsgSender : public MsgSender<Company>
{
public:
using MsgSender<Company>::SendClear; //using 선언
void SendClearMsg(const MsgInfo& info)
{
//메세지 전송 전 정보를 로그에 기록
SendClear(info);
//메세지 전송 후 정보를 로그에 기록
}
};
3.호출할 함수가 기본 클래스의 함수라는 점을 명시적으로 지정한다.
template<typename Company>
class LoggingMsgSender : public MsgSender<Company>
{
public:
void SendClearMsg(const MsgInfo& info)
{
//메세지 전송 전 정보를 로그에 기록
MsgSender<Company>::SendClear(info); //명시적으로 MsgSender의 함수 호출
//메세지 전송 후 정보를 로그에 기록
}
};
마지막 3번째 방법은 어지간하면 사용하지 말자. 호출되는 함수가 가상 함수인 경우가 있기 때문이다. 이런 식으로 명시적 한정을 해 버리면 가상 함수 바인딩이 무시되기 때문이다.
이 세 가지 방법은 기본 클래스 템플릿이 이후에 어떻게 특수화되더라도 일반형 템플릿에서 제공하는 인터페이스를 그대로 제공할 것이라고 컴파일러에게 약속을 하는 것이다. 하지만, 특수화된 템플릿 매개변수가 들어왔을 때는 컴파일이 안될 수 있다. CompanyC 클래스가 들어왔을 때 기본 클래스 템플릿에는 SendClear 함수가 없기 때문에 당연한 것이다.
이것만은 잊지 말자
파생 클래스 템플릿에서 기본 클래스 템플릿의 이름을 참조할 때는, this-> 접두사를 붙이거나 기본 클래스 한정문을 명시적으로 써 주는 것으로 해결하자.