728x90
반응형

템플릿 클래스를 기본 클래스로 파생 시키는 클래스를 구현 할 상황이 생겼다고 가정해보자.

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로 이름을 변경했지만, 이 코드는 문제가 존재한다.

기본 클래스에 있음에도 컴파일러는 기본 클래스를 들여다보려고 하지 않기때문에, 파생 클래스인 LogginMsgSender에서 SendClearMsg 함수 안의 SendClear함수는 존재하지 않는 취급을 받아 컴파일 되지 않는다.

 

완전 템플릿 특수화

완전 템플릿 특수화는 일반형 템플릿 매개변수가 특정 변수 일 때 사용할 수 있도록 특수화 하는 것이다.

class CompanyZ
{
public:
	void SendEncrypted(const string& msg);
};

위 코드의 CompanyZ는 MsgSender 템플릿을 사용하기엔 문제가 있다. MsgSender 템플릿 클래스의 SendClear 함수 안의 SendClearText 함수가 없기 때문이다.

결과적으로 MsgSender 템플릿을 사용할 때 템플릿 매개변수가 CompanyZ일 경우에 SendClearText함수가 사용되지 않도록 전용 함수를 지정해주면 좋을 것 같은데, 그래서 이 때 사용하는 것이 완전 템플릿 특수화이다.

template<> //완전 템플릿 특수화 선언
class MsgSender<CompanyZ> //템플릿 매개변수가 CompanyZ일 경우
{
public:
	void SendSecret(const MsgInfo& info);
};

template<> 처럼 괄호안에 아무것도 없는 template의 뜻은 템플릿도 아니고 클래스도 아니라는 것이다.

즉, 위 코드는 MsgSender 템플릿을 템플릿 매개변수가 CompanyZ일 때 쓸 수 있도록 특수화 한 버전이며, CompanyZ외 다른 매개변수로는 사용 할 수 없다.

 

template<typename Company>
class LoggingMsgSender : public MsgSender<Company> //템플릿 클래스의 파생 클래스
{
public:
	void SendClearMsg(const MsgInfo& info)
	{
		//메세지 전송 전 정보를 로그에 기록
		
        SendClear(info); // Company == CompanyZ라면 이 함수는 존재조차 하지 않는다.
        
		//메세지 전송 후 정보를 로그에 기록
	}
};

다시 처음의 문제로 돌아가서, 위의 코드에서 기본 클래스가 MsgSender<CompanyZ>이면 이 코드는 말이 되지 않는다.

MsgSender<CompanyZ> 클래스에는 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);
		//메세지 전송 후 정보를 로그에 기록
	}
};

지금 경우와 항목 33에서 using 선언으로 해결 한 것 자체는 똑같지만, 해결한 문제는 다르다.

항목 33처럼 기본 클래스의 이름이 파생 클래스에서 가려지는 것이 아니라, 기본 클래스(템플릿화된)의 유효범위를 뒤지라고 컴파일러에게 알려주는 것이다.

 

3. 호출 할 함수가 기본 클래스의 함수라는 점을 명시적으로 지정한다.

template<typename Company>
class LoggingMsgSender : public MsgSender<Company>
{
public:
	void SendClearMsg(const MsgInfo& info)
	{
		//메세지 전송 전 정보를 로그에 기록
		MsgSender<Company>::SendClear(info); //명시적으로 MsgSender의 함수 호출
		//메세지 전송 후 정보를 로그에 기록
	}
};

이 방법은 되도록 사용하지 말자. 호출되는 함수가 가상함수인 경우, 이런식으로 명시적 한정을 해버리면 가상 함수 바인딩이 무시되기 때문이다.

 

이 세가지 방법은 기본 클래스 템플릿이 이후에 어떻게 특수화 되더라도 일반형 템플릿에서 제공하는 인터페이스를 그대로 제공할 것이라고 컴파일러에게 약속 하는 것이다. 하지만 CompanyZ 클래스가 들어왔을 때 기본 클래스 템플릿에는 SendClear함수가 없기 때문에 당연히 컴파일이 되지않는것처럼, 특수화된 템플릿 매개변수가 들어왔을 때는 컴파일이 안될 수도 있다는 점을 기억해야한다.

 

요약

  • 파생 클래스 템플릿에서 기본 클래스 템플릿의 이름을 참조할 때는, this->를 접두사로 붙이거나 기본 클래스 한정문을 명시적으로 써 주는 것으로 해결하자.
728x90
반응형