본문 바로가기
이론/[책]Effective C++

[항목43]템플릿으로 만들어진 기본 클래스 안의 이름에 접근하는 방법을 알아 두자

by 사과잼빵 2017. 12. 25.

template <typename Company>

class MsgSender<Company>

{

public:

  void sendClear(const MsgInfo& info)

  {

      동작...

  }

}


template<typename Company>

class LoggingMsgSender : public MsgSender<Company>

{

public:

  void sendClearMsg(const MsgInfo& info)

  {

    sendClear(info);

  }

}


위의 코드는 컴파일되지 않는다.

그 이유는 LoggingMsgSender 클래스가 컴파일될 때 Base클래스인 MsgSender의 템플릿 매개변수가 어떤 것인지 모르기 때문에(LoggingMsgSender가 인스턴스로 만들어 질 때까지 무엇인지 알 수 없다) MsgSender<Company> 클래스가 어떤 형태인지 알 수 없어서 sendClear 함수가 들어 있는지 없는지 알아낼 방법이 없기 때문입니다.


Base 클래스인 MsgSender에 sendClear 함수가 버젓이 있는데 무슨 소리인지 이해가 가지 않는 분들도 계실겁니다. 왜 컴파일러에서 이런 제약을 걸게 되었는지에 대해서는 여러 원인들이 있겠지만 한가지 예는 완전템플릿 특수화가 있습니다.

- 완전 템플릿 특수화(total template specialization)

template<>

class MsgSender<CompanyZ>

{

public:

  void sendSecret(const MsgInfo& info)

  {

    ...

  }

}

MsgSender클래스가 전달받은 템플릿 매개변수가 CompanyZ 일 때 쓸수 있도록 특수화된 버전입니다. 


위와 같이 특수화된 버전의 MsgSender의 경우 SendClear함수가 아예 존재하지 않습니다. MsgSender<CompanyZ>를 상속받은 LoggingMsgSender의 경우 sendClear 함수를 호출 할 수 없기 때문에 위와 같이 템플릿으로 만들어진 기본클래스의 함수 호출을 컴파일러에서 막아놓은 것입니다.


위의 코드를 컴파일 가능하게 바꾸는 방법은 세 가지가 있습니다.


1. 기본 클래스 함수에 대한 호출문 앞에 this->를 붙입니다.

template<typename Company>

class LoggingMsgSender : public MsgSender<Company>

{

public:

  void sendClearMsg(const MsgInfo& info)

  {

    this->sendClear(info);  //sendClear가 상속되는 것으로 가정

  }

}


2. using 선언을 사용합니다.

template<typename Company>

class LoggingMsgSender : public MsgSender<Company>

{

public:

  using MsgSender<Company>::sendClear;  //컴파일러에게 sendClear 함수가 기본                                                              클래스에 있다고 가정하라고 알려준다.

  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);  //sendClear가 상속되는 것으로 가정

  }

}


위의 세가지 방법들은 모두 동작 원리가 같습니다. 기본 클래스 템플릿이 이후에 어떻게 특수화되더라도 원래의 일반형 템플릿에서 제공하는 인터페이스를 그대로 제공할 것이라고 컴파일러에게 약속하는 것입니다.


LoggingMsgSender<CompanyZ> zMsgSender;

MsgInfo msgData;

zMsgSender.sendClearMsg(msgData);

혹시나 위와 같이 약속을 어기게 되더라도 CompanyZ라는 템플릿 매개변수를 전달받아 인스턴스화될 때  MsgSender<CompanyZ>에는 sendClear라는 함수가 없다는걸 컴파일러가 알아 챌 수 있으므로 컴파일이 불가능합니다. 


파생 클래스 템플릿의 정의가 구문분석될 때 ( C++ 기본 정책)  

->  파생 클래스 템플릿이 특정한 템플릿 매개변수를 받아 인스턴스화될 때 

로 진단시점이 변경되었다.


결과적으로 기본 클래스 멤버에 대한 참조가 무효한지에 대한 컴파일러의 진단 시점이 변경된 것이라고 볼 수 있습니다.