스토리텔링 개발자

[Effective C++] 43. 템플릿 부모 클래스의 인터페이스에 접근하기 본문

개발/Effective C++

[Effective C++] 43. 템플릿 부모 클래스의 인터페이스에 접근하기

김디트 2024. 7. 15. 11:16
728x90

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

 

 

 

템플릿 클래스에서 생길 수 있는 문제
class CompanyA
{
public:
    void sendCleartext(const string& msg);
    void sendEncrypted(const string& msg);
    ...
};

class CompanyB
{
public:
    void sendCleartext(const string& msg);
    void sendEncrypted(const string& msg);
    ...
};

class MsgInfo { ... };

// 템플릿 기본 클래스
template<typename C>
class MsgSender
{
public:
    void sendClear(const MsgInfo& info)
    {
        string msg = ... // info로부터 만든 msg
        
        C c;
        c.sendCleartext(msg); // sendCleartext 인터페이스를 요구
    }
    ...
};

// MsgSender<C>를 기본 클래스로 가지는 템플릿 클래스
templace<typename C>
class LoggingMsgSender : public MsgSender<C>
{
public:
    void senderClearMsg(const MsgInfo& info)
    {
        ...
        sendClear(info); // 이 코드로 인해 컴파일이 되지 않는다!!!
        ...
    }
    ...
};
  • sendClear를 찾을 수 없다는 것이 컴파일이 안 되는 이유이다.

 

 

 

문제 이유
  • 기본 클래스인 MsgSender<C>에서 C는 템플릿 매개변수이고, 이가 명확하지 않은 시점에서는 sendClear 함수가 들어있는지 알아낼 방법이 없는 것이 문제이다.
  • 아래와 같은 경우, sendClear가 없는 MsgSencer<C>라는 것이 존재할 수 있다.
// sendCleartext 함수를 제공하지 않는 클래스
class CompanyZ
{
public:
    ...
    void sendEncrypted(const string msg);
    ...
};

// MsgSender 템플릿의 완전 특수화 버전
// sendClear 함수를 제외하고 특수화 해버렸다.
template<>
class MsgSender<CompanyZ>
{
public:
    ...
    void sendSecret(const MsgInfo& info) { ... }
};
  • 기본 클래스 템플릿은 언제라도 특수화 될 수 있고, 특수화 버전 인터페이스는 원래의 일반형 템플릿과 꼭 같으리란 보장이 없다.
  • 즉, 객체 지향 C++과는 다르게 템플릿 C++ 에서는 상속 메커니즘이 다르다.

 

 

 

완전 템플릿 특수화
  • 템플릿의 매개변수들이 하나도 빠짐없이 구체적인 타입으로 정해진 상태의 특수화.

 

 

 

해결 방법
  • C++의 "난 템플릿화된 기본 클래스는 멋대로 안 뒤질 거야!" 가 발현되지 않도록 해야 한다.
  • 해결 방법 세 가지
    1. 기본 클래스 함수에 대한 호출문 앞에 ‘this->’를 추가한다.
      • templace<typename C>
        class LoggingMsgSender : public MsgSender<C>
        {
        public:
            void sendClearMsg(const MsgInfo& info)
            {
                this->sendClear(info); // sendClear가 상속되는 것으로 가정한다.
                ...
            }
        };
    2. using 선언을 한다.
      • templace<typename C>
        class LoggingMsgSender : public MsgSender<C>
        {
        public:
            // 컴파일러에게 기본 클래스에 해당 함수가 있다고 가정하라고 알려준다.
            using MsgSender<C>::sendClear;
            
            void sendClearMsg(const MsgInfo& info)
            {
                sendClear(info);
                ...
            }
        };
      • 항목 33과 비슷해 보이지만..
        • 항목 33의 경우, 기본 클래스의 이름이 파생 클래스에서 가려져서 기본 클래스를 탐색하지 않는 문제.
        • 해당 상황의 경우, 템플릿화된 기본 클래스는 아무 말이 없으면, 어떤 경우에도 탐색 유효범위에 들어가지 않는 문제.
    3. 호출 함수가 기본 클래스 함수라는 것을 명시적으로 지정한다.
      • template<typename C>
        class LoggingMsgSender : public MsgSender<C>
        {
        public:
            void sendClearMsg(const MsgInfo& info)
            {
                // sendClear 함수가 상속되는 것으로 가정된다.
                MsgSender<C>::sendClear(info);
                ...
            }
        };
      • 하지만 이런 방법은 비추천.
        • 호출되는 함수가 가상함수인 경우에는 가상 함수 바인딩이 무시되기 때문이다.
  • 세 가지 방법 모두 동작 원리가 같다.
    • 기존 클래스 템플릿이 어떻게 특수화 되더라도 원래의 일반형 템플릿에서 제공하는 인터페이스를 그대로 제공할 것이라고 컴파일러에게 약속하는 것이다.
  • 즉, 특수화에서 인터페이스를 제공하지 않으면 컴파일 에러가 발생한다.
    • LoggingMsgSender<CompanyZ> zMsgSender;
      MsgInfo msgData;
      ...
      zMsgSender.sendClearMsg(msgData); // 에러!!!!
  • 즉, 결국 기본 클래스 멤버에 대한 참조가 무효한지 컴파일러가 진단하는 위치를 정하는 것이 핵심이다.
    1. 파생 클래스 템플릿의 정의가 구문분석될 때
      • 이른 진단(early diagnose)
      • C++의 기본 정책
    2. 파생 클래스 템플릿이 특정한 템플릿 매개변수를 받아 인스턴스화 될 때
      • 위 세 가지 방법을 통해 해결한 방법
728x90
Comments