목차

     

     


     

     

     

    Chapter 7 템플릿과 일반화 프로그래밍


     

     

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

     

    Q. 다음 문장 중 틀린 것을 고르시오.

    1.  클래스 및 템플릿은 모두 인터페이스와 다형성을 지원한다.

    2.  클래스의 경우, 인터페이스는 명시적이며 함수의 시그너처를 중심으로 구성되어 있다.

    3.  다형성은 프로그램 실행 중에 가상 함수를 통해 나타난다.

    4.  인스턴스화를 진행하는 함수 템플릿에 어떤 템플릿 매개변수가 들어가느냐에 따라 호출되는 함수가 달라지는데 이를 컴파일 타임 다형성이라고 한다.

    5.  암시적 인터페이스는 함수 시그너처에 기반하고 있지 않다.

    6.  템플릿 매개변수의 경우, 인터페이스는 명시적이며 유효 표현식에 기반을 두어 구성됩니다.

    7.  컴파일 중에 템플릿 인스턴스화와 함수오버로딩 모호성 해결은 다형성의 예이다.  

    더보기

    답:  6번. 템플릿 매개변수의 경우, 인터페이스는 암시적이며 유효 표현식에 기반을 두어 구성됩니다.

    • 명시적 인터페이스는 대개 함수 시그너처로 이루어집니다. 함수 시그너처는 함수의 이름, 매개변수 타입, 반환 타입 등을 통틀어 부르는 용어입니다.
    • 암시적 인터페이스는 함수 시그너처에 기반하고 있지 않습니다. 암시적 인터페이스를 이루는 요소는 유효 표현식입니다.

     

     

    Q.  다음 중 틀린 명제는 몇 번일까요?

    1.  클래스 및 템플릿은 모두 인터페이스와 다형성을 지원한다.

    2.  클래스의 경우, 인터페이스는 명시적이며 함수의 시그니처를 중심으로 구성되어 있다. 
         다형성은 프로그램 실행 중에 가상 함수를 통해 나타난다.

    3.  템플릿 매개변수의 경우, 인터페이스는 암시적이며 유효 표현식에 기반을 두어 구성된다.    
         다형성은 컴파일 중에 템플릿 인스턴스화와 함수 오버라이딩 모호성 해결을 통해 나타난다.

    더보기

    답:  3번. 오버라이딩이 아닌 오버로딩 모호성 해결을 통해 나타난다.

     

     

    Q. 다음 중 틀린 것을 모두 고르세요.

    templete<typename T>
    void doProcessing(T& w)
    {
    	if(w.size() > 0.3f && w != tmpWidget)
    	{
    		...
    	}
    }

    1.  암시적 인터페이스는 컴파일 도중에 점검 된다.

    2.  암시적 인터페이스는 함수 시그니처에 기반한다.

    3.  암시적 인터페이스는 유효 표현식에 기반한다.

    4.  예시 코드의 템플릿 T(w의 타입)는 size() 함수를 제공해야 한다.

    5.  예시 코드의 템플릿 T(w의 타입)는 operator !=함수를 지원하지 않아도 상관 없다.

    더보기

    답:  2번

    1. 암시적, 명시적 인터페이스의 공통점
    2. 암시적 인터페이스는 함수 시그니처에 기반하지 않는다 (명시적 인터페이스는 기반함)
    3. 맞다
    4. 제공해야 한다. 하지만 수치 타입을 반환할 필요까지는 없다.
    5. 지원하지 않아도 된다.

    < 테스트 >

    class Widget
    {
    //public:
    //	float size()
    //	{
    //		return 0;
    //	}
    //	bool operator!=(const Widget& cmp)
    //	{
    //		return true;
    //	}
    };
    
    Widget tmpWidget;
    
    template<typename T>
    void doProcessing(T& w)
    {
    	if (w.size() > 0.3f && w != tmpWidget)
    	{
    		
    	}
    }
    
    int main(void)
    {
    	Widget w;
    	doProcessing(w);
    }

    유효성이 없을 때, 컴파일 단계에서 오류가 난다.

     

     

    Q. 명시적 인터페이스는 어떤 요소로 결정되는가?

    더보기

    답:  함수 시그니처 (이름, 반환타입, 매개변수타입, 함수의 상수성 여부

    암시적 인터페이스는 어떤 요소로 결정되는가?

    더보기

    답:  유효 표현식

    런타임 다형성과 컴파일 다형성의 차이에 대해 설명하라.

    더보기

    정답:

    • 런타임 다형성: 프로그램 실행 중에 일어나는 가상 함수 바인딩
    • 컴파일 다형성: 컴파일 중에 호출할 함수를 오버로딩하는 것. 컴파일 도중에 인스턴화가 일어난다. 언리얼 엔진 컴파일 시점에 CDO를 생성하는데 CDO 또한 인스턴스라고 볼 수 있다.
    • 위에서 함수들의 호출을 성공시키기 위해 템플릿의 인스턴스화가 일어나는데(컴파일 도중에 진행됨), 어떤 템플릿 매개변수가 들어가느냐에 따라 호출되는 함수가 달라지기 때문에, 이를 컴파일 타임 다형성이라고 한다.

     

     


     

     

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

     

    Q.  다음 중 틀린것을 고르시오.

    1.  C++의 관점에서, 템플릿 선언문에 쓰이는 class와 typename은 완전히 같은 의미다.

    2.  템플릿 내의 이름 중, 매개변수에 종속된 것을 가리켜 의존 이름(dependent name)이라고 한다.

    3.  의존 이름이 어떤 클래스 안에 중첩되어 있는 경우의 이름을 중첩 의존 이름(nested dependent name)이라고 부른다.

    4.  C++ 구문 분석기는 중첩 의존 이름을 기본적으로 타입이라고 해석한다.

    5.  중첩 의존 이름이 기본 클래스 리스트에 있거나 멤버 초기화 리스트 내의 기본 클래스 식별자로있는 경우에는 중첩 의존 타입 이름을 식별하는 용도로 typename을 사용하지 않는다. 

    6.  비의존 이름(non-dependent name)은 템플릿 매개변수가 어떻든 상관없는 타입 이름이다.  

    더보기

    답:  4번. C++ 구문 분석기는 중첩 의존 이름을 기본적으로 타입이 아닌 것으로 해석한다. (302~303쪽 참고).

    템플릿 안에서 중첩 의존 이름을 참조할 경우에는, 그 이름 앞에 typename 키워드를 붙여 줘야 합니다.

     

     

    Q. 아래의 코드를 올바르게 고치기 위해 [   ㄱ   ]에 들어갈 키워드를 적으세요.

    template<typename C>
    void print2nd(const C& container)
    {
    	if (container.size() >= 2) {
        	[  ㄱ  ] C::const_iterator iter(container.begin());
            
            ++iter;
            int value = *iter;
            cout << value;
        }
    }
    더보기

    답:  typename (303쪽 참고)

    iter의 선언이 선언으로서 의미가 있으려면 C::const_iterator가 반드시 타입이어야 하는데, 우리는 C++ 컴파일러에게 타입이라고 알려주지 않았으니, C++는 제멋대로 타입이 아닌 것으로 가정해 버립니다.

    그러므로 typename을 붙여 C::const_iterator 가 타입이라고 알려줘야 합니다.

     

     

    Q. 다음 빈 칸에서 typename을 써도 되는 곳, 꼭 써야하는 곳과 쓰지 않아야 하는 곳은? (아래 구문은 컴파일 X)

    template<1.typename C>
    void f(const 2.typename C& container, 3.typename C::iterator iter);
    void d(4.typename C Invalue);
    더보기

    답: 

    1번 : 쓸 수 있다. typename 대신에 ‘class’를 써도 된다. 하지만 둘 중 하나는 꼭 써줘야 한다.

    3번 : 중첩 의존 이름이기 때문에 꼭 써야 한다. 
    2번, 4번은 쓸 수 없다. 중첩 의존 타입 이름이 아니기 때문에 변수명인지, 타입이름인지 명시적으로 적어줘야 컴파일러가 혼동하지 않습다.

     

     

    다음 중 틀린 명제는 무엇일까요??

    1. C++에서 템플릿의 타입 매개변수를 선언할 때는 class와 typename의 뜻이 완전히 똑같다.
    2. 중첩 의존 타입 이름을 식별하는 용도에는 반드시 typename을 사용한다.
    3. 중첩 의존 이름이 기본 클래스 리스트에 있거나 멤버 초기화 리스트 내의 기본 클래스 식별자로 있는 경우에도 typename을 사용한다.
    더보기

    답: 


     

     

     

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

     

    Q. 아래의 코드에서 문제가 되는 부분을 찾고 올바르게 고치십시오.

    #include <string>
    using namespace std;
    
    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 Company>
    class MsgSender{
    public:
    	void sendClear(const MsgInfo& info)
    	{
    		string msg;
    
    		Company c;
    		c.sendCleartext(msg);
    	}
    
    	void sendSecret(const MsgInfo& info)
    	{ /* sendClear 함수와 유사. 단, c.sendEncrpted 함수를 호출하는 점이 차이 */ }
    };
    
    template<typename Company>
    class LoggingMsgSender : public MsgSender<Company>
    {
    public:
    	void sendClearMsg(const MsgInfo& info)
    	{
    		// "메시지 전송 전" 정보를 로그에 기록합니다.
    
    		sendClear(info);
    
    		// "메시지 전송 후" 정보를 로그에 기록합니다.
    	}
    };

     

    더보기

    답:  코드 마지막의 class LoggingMsgSender 클래스 부분이 문제가 되어 컴파일 되지 않습니다. 'sendClear 함수가 존재하지 않는다'라는 것이 컴파일이 안 되는 이유입니다. (308쪽)

    • 컴파일러가 LoggingMsgSender 클래스 템플릿의 정의와 마주칠 때, 컴파일러는 대체 이 클래스가 어디서 파생된 것인지를 모릅니다.
    • MsgSender<Company>에서 파생되었지만 Company는 템플릿 매개변수이고, 이 템플릿 매개변수는 LoggingMsgSender가 인스턴스로 만들어질 때까지 무엇이 될지 알 수 없습니다.
    • Compnay가 정확히 무엇인지 모르는 상황에서는 MsgSender<Company> 클래스가 어떤 형태인지 알 방법이 없습니다. 이러니, sendClear 함수가 들어 있는지 없는지 알아낼 방법이 없습니다. 

    [ 해결방안 ]

    1.  기본 클래스 함수에 대한 호출문 앞에 "this->" 붙이기  

    • 파생 클래스 템플릿에서 기본 클래스 템플릿의 이름을 참조할때, "this->"를 접두사로 붙입니다. 
    template<typename Company>
    class LoggingMsgSender : public MsgSender<Company>
    {
    public:
    	void sendClearMsg(const MsgInfo& info)
    	{
    		// "메시지 전송 전" 정보를 로그에 기록합니다.
    
    		this->sendClear(info);
    
    		// "메시지 전송 후" 정보를 로그에 기록합니다.
    	}
    };

     

    2. using 선언을 사용

    template<typename Company>
    class LoggingMsgSender : public MsgSender<Company>
    {
    public:
    	using MsgSender<Company>::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);
    
    		// "메시지 전송 후" 정보를 로그에 기록합니다.
    	}
    };

     

     

    완전 템플릿 특수화 -  CompanyZ

    #include <string>
    using namespace std;
    
    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 Company>
    class MsgSender{
    public:
    	void sendClear(const MsgInfo& info)
    	{
    		string msg;
    
    		Company c;
    		c.sendCleartext(msg);
    	}
    
    	void sendSecret(const MsgInfo& info)
    	{ /* sendClear 함수와 유사. 단, c.sendEncrpted 함수를 호출하는 점이 차이 */ }
    };
    
    class CompanyZ{
    public:
    	void sendEncrypted(const string& msg);
    };
    
    // CompanyZ를 위한 MsgSender의 특수화 버젼
    template<>
    class MsgSender<CompanyZ>{
    public:
    	void sendSecret(const MsgInfo& info) { }
    };

     

     

    항목 44:  매개변수에 독립적인 코드는 템플릿으로부터 분리시키자

     

    Q. 다음 중 템플릿에 대한 설명으로 틀린 것은?

    1.  템플릿 매개변수에 종속되지 않은 템플릿 코드는 비대화의 원인이 됩니다.

    2.  비타입 템플릿 매개변수로 생기는 코드 비대화의 경우, 템플릿 매개변수를 함수 매개변수 혹은 클래스 테이터 멤버로 대체함으로써 비대화를 종종 없앨 수 있습니다.

    3.  타입 매개변수로 생기는 코드 비대화의 경우, 동일한 이진 표현구조를가지고 인스턴스화되는 타입들이 한 가지 함수 구현을 공유하게 만듦으로써 비대화를 감소시킬 수 있습니다.

    4.  템플릿 코드에서는 코드 중복이 명시적입니다.

    더보기

     답:  4번. (314쪽 참고). 템플릿 코드에서는 코드 중복이 암시적입니다.

    소스 코드에는 템플릿이 하나밖에 없기 때문에, 어떤 템플릿이 여러 번 인스턴스화될 때 발생할 수 있는 코드 중복을 사용자의 감각으로 알아채야 합니다.

     

     

    Q.  아래 코드의 문제점을 이야기하고 개선할 수 있는 방법을 제시하세요.

    using namespace std;
    
    template<typename T, size_t n>
    class SquareMatrix{
    public:
    	void invert();
    };
    
    int main()
    {
    	SquareMatrix<double, 5> sm1;
    	sm1.invert();
    
    	SquareMatrix<double, 10> sm2;
    	sm2.invert();
    }
    더보기

    답:  해당 템플릿을 사용하면 T라는 타입 매개변수 뿐만 아니라, size_t 타입의 비타입 매개변수인 n도 받습니다. main문 안에 invert의 사본이 인스턴스화되는데, 만들어지는 사본의 개수가 2개 입니다. 이는 비 효율적입니다.

     

    효율적으로 사용하는 방법 예시

    template<typename T>
    class SquareMatrixBase{
    protected:
    	SquareMatrixBase(size_t n, T* pMem) : size(n), pData(pMem) { }
    
    	void setDataPtr(T* ptr) { pData = ptr; }
    
    private:
    	size_t size; // 행렬의 크기
    
    	T* pData;    // 행렬 값에 대한 포인터
    };
    
    template<typename T, size_t n>
    class SquareMatrix : private SquareMatrixBase<T>{
    public:
    	SquareMatrix() : SquareMatrixBase<T>(n, data) { }
    
    	void invert();
    
    private:
    	T data[n * n];
    };
    
    int main()
    {
    	SquareMatrix<double, 5> sm1;
    	sm1.invert();
    
    	SquareMatrix<double, 10> sm2;
    	sm2.invert();
    }

     


     

     

     

    항목 45:  "호환되는 모든 타입"을 받아들이는 데는 멤버 함수 템플릿이 직방!

     

    Q.  포인터에는 스마트 포인터로 대신할수 없는 특징이 있습니다. 무엇일까요?

    더보기

    답:  포인터는 스마트 포인터와 달리 암시적 변환을 지원합니다. (321쪽 참고) 

    파생 클래스 포인터는 암시적으로 기본 클래스 포인터로 변환되고, 비상수 객체에 대한 포인터는 상수 객체에 대한 포인터로의 암시적 변환이 가능합니다. 

    class Top { };
    
    class Middle : public Top {
    };
    
    class Bottom : public Middle {
    };
    
    int main(){
    	Top* pt1 = new Middle;	// Middle*의 Top* 변환
    	Top* pt2 = new Bottom;	// Bottom*의 Top* 변환
    
    	const Top* pct2 = pt1;	// Top*의 const Top* 변환
    }

     

     

    Q.  멤버 함수 템플릿(member function template)에 대해 설명해보세요.

    더보기

    답:  멤버 함수 템플릿은 어떤 클래스의 멤버 함수를 찍어내는 템플릿입니다.

    template<typename T>
    class SmartPtr{
    public:
    	template<typename U>
    	SmartPtr(const SmartPtr<U>& other);
    };

    위의 예제는 "일반화된 복사 생성자"를 만드는 멤버 템플릿입니다. (322쪽 참고)

     

    template<typename T>
    class SmartPtr{
    public:
    	template<typename U>
    	SmartPtr(const SmartPtr<U>& other) : heldPtr(other.get()) { }
        
        T* get() const { return heldPtr; }
        
    private:
        T* heldPtr;
    };

    T* 타입의 포인터를 SmartPtr<U>에 들어 있는 U* 타입의 포인터로 초기화했습니다. U* 에서 T*로 진행되는 암시적 변환이 가능할 때만 컴파일 에러가 나지 않습니다.
    SmartPtr<T>의 일반화 복사생성자는 호환되는 타입의 매개변수를넘겨받을 때만 컴파일되도록 만들어졌습니다.


     

     

    항목 46 : 타입 변환이 바람직할 경우에는 비멤버 함수를 클래스 템플릿 안에 정의해 두자

     

    template <typename T> class Rational;
    template <typename T>
    const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs);
    
    template <typename T>
    class Rational
    {
    public:
    	friend const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs)
    	{
    		return doMultiply(lhs, rhs);
    	}
    };
    
    template<typename T>
    const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs)
    {
    	return Rational<T>(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
    }

     


     

     



    항목 47:  타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자

     

    Q. 아래는 STL deque와 list 내의 반복자 일부를 적은 코드다. 빈칸 [ ㄱ ], [ ㄴ ], [ ㄷ ]에 들어갈 것을 고르시오. 

    template<>
    class deque{
    public:
        class iterator{
        public:
            typedef [  ㄱ  ] iterator_category;
        };
    };
    
    template<>
    class list{
    public:
        class iterator{
        public:
            typedef [  ㄴ  ] iterator_category;
        };
    
    };
    
    template<>
    class string{
    public:
        class iterator{
        public:
            typedef [  ㄷ  ] iterator_category;
        };
    
    };

     

    1.  random_access_iterator_tag  -  bidirectional_iterator_tag  -  random_access_iterator_tag

    2.  bidirectional_iterator_tag  -  random_access_iterator_tag - forward_iterator_tag

    3.  bidirectional_iterator_tag  -  forward_iterator_tag  -  random_access_iterator_tag  
    4.  random_access_iterator_tag  -  forward_iterator_tag  -  bidirectional_iterator_tag  

    5.  forward_iterator_tag  -  random_access_iterator_tag  -  bidirectional_iterator_tag  

    더보기

    답:  1번

    입력 반복자(input iterator):  istream_iterator

    출력 반복자(output iterator):  ostream_iterator

    순방향 반복자(forward iterator):  TR1의 해시 컨테이너

    양방향 반복자(birdirectional iterator):  list, set, multiset, map, multimap

    임의 접근 반복자(random access iterator):  vector, deque, string

     

     template<typename IterT , typename DistT> //반복자에 대해서는 이 구현
     void doAdvance(IterT& iter, DistT d, std::random_access_iterator_tag) 
     {
    		 iter+=d;
     }
     
     template<typename IterT , typename DistT> //양방향 반복자에 대해서는 이 구현
     void doAdvance(IterT& iter, DistT d, std::bidirectional_iterator_tag)
     {
    		 if(d>=0) { while(d--) ++iter; }
    		 else { while(d++) --iter; }
     }
     
     template<typename IterT , typename DistT>  // 입력 반복자에 대해서는 이 구현
     void doAdvance(IterT& iter, DistT d, std::input_iterator_tag)
     {
    		 if(d <0)
    		 {
    				 throw std::out_of_range();
    		 }
    		 while(d--) ++iter;
     }

     

    Q. 위의 코드를 하나의 템플릿으로 몰아넣었을때의 문제점?

    더보기

    A. 템플릿이 컴파일 시 통과하기 때문에 템플릿 안에 if, else 문을 타야합니다. 하지만 위의 코드처럼 템플릿을 여러 개로 나눠 놓고 오버로딩 시키면 컴파일 시점에 iterator_tag에 맞게 매칭되어 컴파일되기 때문에 컴파일 시점에 검사가 되어 좋습니다. 컴파일 시점 이후로 알면 템플릿을 사용하는 이유가 없습니다.


     

     

     

    항목 48:  템플릿 메타프로그래밍, 하지 않겠는가?

     

    Q.  다음 보기 중 틀린 것은?

    1.  템플릿 메타프로그래밍(template metaprogramming, TMP)은 컴파일이 진행되는 동안에 실행되기 때문에, 에러를 컴파일 시점에 발견할 수 있다.  

    2.  템플릿 메타프로그래밍은 튜링 완정성을 가지고 있다. 

    3.  템플릿 메타프로그래밍은 반복(iteraton)을 사용할 수 있지만 재귀(recursion)을 사용할 순 없다.

    4.  템플릿 메타프로그래밍은 '치수 단위의 정확성 확인'과 '행렬 연산의 최적화'에 쓰인다.  

    5.  템플릿 메타프로그래밍은 특정 타입에 대해 부적절한 코드가 만들어지는 것을 막을 수 있다.

    더보기

    답:  3번. 반대다.

     

    TMP 레퍼런스

    https://modoocode.com/221#google_vignette

     

    씹어먹는 C++ - <9 - 3. 템플릿 메타 프로그래밍 (Template Meta programming)>

    모두의 코드 씹어먹는 C++ - <9 - 3. 템플릿 메타 프로그래밍 (Template Meta programming)> 작성일 : 2017-06-26 이 글은 43630 번 읽혔습니다. 이번 강좌에서는타입이 아닌 템플릿 인자템플릿 메타 프로그래밍

    modoocode.com

     

    https://modoocode.com/222

     

    씹어먹는 C++ - <9 - 4. 템플릿 메타 프로그래밍 2>

    모두의 코드 씹어먹는 C++ - <9 - 4. 템플릿 메타 프로그래밍 2> 작성일 : 2017-07-02 이 글은 32654 번 읽혔습니다. 에 대해서 배웁니다. 안녕하세요 여러분! 지난 강좌에서 왜 TMP 를 활용하여 힘들게 힘

    modoocode.com