티스토리 뷰

 

Effective C++ 이펙티브 C++
스콧 마이어스 저/곽용재

 

 

7. 템플릿과 일반화 프로그래밍 (1)

 

 

항목 41 : 템플릿 프로그래밍의 천릿길도 암시적 인터페이스와 컴파일 타임 다형성부터

 

명시적 인터페이스와 런타임 다형성의 예

class Widget

{

public:

Widget();

virtual ~Widget();

virtual std::size_t size() const;

virtual void normalize();

void swap(Widget& other);

};

 

// w는 명시적 인터페이스

void doProcessing(Widget& w)

{

if(w.size() > 10 && w != someNastyWidget)

{

Widget temp(w);

// 가상 함수의 호출은 런타임 다형성에 의해 호출

temp.normalize();

temp.swap(w);

}

}

 

암시적 인터페이스와 컴파일 다형성의 예

template<typename T>

// T는 암시적 인터페이스

void doProcessing(T& w)

{

// operator > 및 operator!= 함수 호출 시 컴파일 타임에 템플릿 인스턴스화 발생

// 컴파일 다형성의 예

if(w.size() > 10 && w != someNastyWidget)

{

T temp(w);

temp.normalize();

temp.swap(w);

}

}

 

  • 클래스 및 템플릿은 모두 인터페이스와 다형성을 지원합니다.
  • 클래스의 경우, 인터페이스는 명시적이며 함수의 시그니처를 중심으로 구성되어 있습니다. 다형성은 프로그램 실행 중에 가상 함수를 통해 나타납니다.
  • 템플릿 매개변수의 경우, 인터페이스는 암시적이며 유효 표현식에 기반을 두어 구성됩니다. 다형성은 컴파일 중에 템플릿 인스턴스화와 오버로딩 모호성 해결을 통해 나타납니다.

 

 

항목 42 : typename의 두 가지 의미를 제대로 파악하자

 

typename 키워드는 중첩 의존 이름만 식별하는 데 써야 합니다.

template<typename C> // typename 쓸 수 있음("class"와 같은 의미)

void if(const C& container, // typename 쓰면 안됨

typename C::iterator iter); // typename 꼭 써야 됨

 

예외

중첩 의존 타입 이름이 기본 클래스의 리스트에 있거나 멤버 초기화 리스트 내의 기본 클래스 식별자로서 있을 경우 typename을 붙여 주면 안됩니다.

template<typename T>

// 상속되는 기본 클래스 리스트: typename 쓰면 안됨

class Derived: public Base<T>::Nested

{

public:

// 멤버 초기화 리스트에 있는 기본 클래스 식별자: typename 쓰면 안됨

explicit Derived(int x) : Base<T>::Nested(x)

{

// 중첩 의존 타입 이름이며 기본 클래스 리스트에도 없고

// 멤버 초기화 리스트의 기본 클래스 식별자도 아님: typename 필요

typename Base<T>::Nested temp;

...

}

...

};

 

 

  • 템플릿 매개변수를 선언할 때, class 및 typename은 서로 바꾸어 써도 무방합니다.
  • 중첩 의존 타입 이름을 식별하는 용도에는 반드시 typename을 사용합니다. 단. 중첩 의존 이름이 기본 클래스 리스트에 있거나 멤버 초기화 리스트 내의 기본 클래스 식별자로 있는 경우에는 예외입니다.

 

 

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

 

예) 컴파일 타임에 어떤 회사로 암호화 또는 비암호화 메시지를 보낼지 결정하는 응용프로그램

class CompanyA{

public:

...

void sendCleartext(const std::string& msg);

void sendEncrypted(const std::string& msg);

...

};

 

class CompanyB{

public:

...

void sendCleartext(const std::string& msg);

void sendEncrypted(const std::string& msg);

...

};

 

// 메시지 생성에 사용되는 정보를 담기 위한 클래스

class MsgInfo{ ... };

 

template<typename Company>

class MsgSender{

public:

// 생성자, 소멸자 등등

...

void sendClear(const MsgInfo& info)

{

std::string msg;

// info로부터 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); // 기본 클래스의 함수를 호출하는데, 이 코드는 컴파일되지 않음

// "메시지 전송 후" 정보를 로그에 기록

}

...

};

 

 

해결1

template<typename Company>

class LoggingMsgSender: public MsgSender<Company>{

public:

...

void sendClearMsg(const MsgInfo& info)

{

this->sendClear(info);

}

};

 

 

해결2

template<typename Company>

class LoggingMsgSender: public MsgSender<Company>{

public:

// 컴파일러에게 sendClear 함수가 기본 클래스에 있다고 가정하라고 알려줌

using MsgSender<Company>::sendClear;

...

void sendClearMsg(const MsgInfo& info)

{

this->sendClear(info);

}

};

 

 

해결3

template<typename Company>

class LoggingMsgSender: public MsgSender<Company>{

public:

...

void sendClearMsg(const MsgInfo& info)

{

MsgSender<Company>::sendClear(info);

}

};

 

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

 

 

- James Song

댓글