STL - 해당되는 글 1건

0.    시작하기 전에 알아두면 좋은 이야기 ? 값과 타입이야기

 

1, 2, 3 또는 3.4 같은 것들은 이라고 부릅니다. 또한, int, double, char 같은 것들은 값이 아닌 타입이라고 부르지요.  값 자체는 함수 오버로딩에 사용될 수 없지만 타입은 함수 오버로딩에 사용될 수 있습니다. ,

void foo( int n) {}

 

void goo( int n) {}

void goo( double n) {}

 

int main()

{

        foo(1);

        foo(2); // 1,2는모두int라는동일타입입니다. 따라서같은함수를호출합니다.

 

        int n = 10;

        double d = 2.3;

 

        goo(n);

        goo(d); // nd는타입이다르므로각각다른함수가호출됩니다.

}

 

당연한 이야기 같지만, 이 사실이 뒤에서 중요하게 등장하게 되니까, 잘 기억하시길

 

1.    Max function template

 

먼저 아래의 template Max()함수를 보도록 하지요.

#include <iostream>

using namespace std;

 

template<typename T>

const T& Max( const T& a, const T& b )

{

        return a < b ? b : a;

}

 

int main()

{

        int a = 1;

        int b = 2;

 

        int x = Max(a, b);  // (A)

        cout << x << endl;

 

        int* p = Max(&a, &b); // (B)

        cout << *p << endl;

}

                                                                                

A의 경우는 당연히 T int로 되므로 아무 문제가 없습니다. 하지만 B의 경우는 T int*로 되므로 결국 값이 아닌 포인터 끼리의 비교를 하는 코드가 됩니다.

 

이제 좀더 똑똑한 Max()를 구현해 보도록 하겠습니다. , 값이 전달되면 그냥 < 연산을 수행하고 포인터가 전달되면 포인터가 가르키는 값을 참조해서 < 연산을 수행하도록 Max를 변경해 보도록 하지요.. 아래와 같은 모양이 될 것입니다.

 

template<typename T>

const T& Max( const T& a, const T& b )

{

        if ( T is Pointer )

               return *a < *b ? b:a;

 

        return a < b ? b : a;

}

 

이제 문제는 T가 포인터인지 아닌지를 알아 내어야 합니다.

 

2.    is_pointer<>의 구현

 

임의의 타입 T가 포인터인지 알아 내려면 template 부분 전문화(partial specialization, 부분특화) 문법을 사용하면 쉽게 구현할 수 있습니다.

아래 처럼 구현 하면 됩니다.

 

template<typename T> struct is_pointer

{

        enum { value = false };

};

 

template<typename T> struct is_pointer<T*>

{

        enum { value = true };

};

 

template<typename T> struct is_pointer<const T*>

{

        enum { value = true };

};

 

// is_pointertest하기위한함수.

template<typename T> void test( const T& a)

{

        if ( is_pointer<T>::value )

               cout << "T is pointer" << endl;

        else

               cout << "T is not pointer" << endl;

}

 

int main()

{

        int x = 0;

 

        test(x);

        test(&x);

 

        int y[10];

        test(y);       // y는배열입니다.

}

 

먼저 template 기본 버전(primary template)을 사용해서 is_pointer의 기본 모양을 만듭니다. 이때 내부적으로는 value 라는 이름의 enum 상수를 넣는데 기본적으로는 false 로 초기화 합니다.

 

이제 T* const T* 에 대해서 template 부분 특화 버전을 제공하는데 value true로 초기화 합니다.

따라서

           is_pointer<T>::value

 

에서 T가 포인터가 아니면 is_pointer의 기본 버전을 사용하므로 false T가 포인터 라면 부분 특화 버전을 사용하므로 true가 나오게 됩니다.

 

이제 우리는 함수 template안에서 T가 포인터 인지 아닌지를 구별할 수 있게 되었습니다.

 

여기서 또 하나 중요한 사실은 is_pointer<T>::value true인지 false인지는 실행시간이 아닌 컴파일시간에 결정 됩니다. , 아래의 문장은

          

      if ( is_pointer<T>::value )

               cout << "T is pointer" << endl;

        else

        cout << "T is not pointer" << endl;

 

T가 포인터가 아니라면 컴파일 후에는

 

      if ( false )

               cout << "T is pointer" << endl;

        else

        cout << "T is not pointer" << endl;

 

 

가 됩니다. if() 문 안의 구문이 변수가 아닌 상수이기 때문에 컴파일러 최적화에 의해서 다시 아래 처럼 최적화된 코드가 생성됩니다.

 

cout << "T is not pointer" << endl;

 

결론은 is_pointer<>를 사용해도 실행시 성능상의 오버헤드가 없다는 이야기 입니다.

, 컴파일 시간이 조금 오래 걸리긴 합니다.

일 때 is_pointer<>는 일반 함수와 닮은 점이 있습니다.

 

int a = square(5);

bool b = is_pointer<T>::value;

 

일반 함수 square()실행시간에 5라는 값을 인자로 전달해서 함수 결과를 리턴해 줍니다.

하지만 is_pointer<T>::value 는 컴파일 시간에 T라는 타입을 인자로 전달해서 bool(또는 다른 타입인 경우도 있습니다.)을 리턴해 주고 있습니다.

이처럼 컴파일시간에 타입을 인자로 가지는 함수를 보통 메타 함수라고 부릅니다.

 

3.    Is_pointer<> Max templte의 결합

 

, 이제 is_pointer<>가 완성되었으므로 Max() 를 다시 구현해 보도록 하겠습니다.

 

 

template<typename T>

const T& Max( const T& a, const T& b )

{

        if ( is_pointer<T>::value )

               return *a < *b ? b : a;

 

        return a < b ? b : a;

}

 

int main()

{

        int a = 1, b = 2;

 

        Max(&a, &b); // (A) 포인터를전달할경우

        // Max(a, b);   // (B) 값을전달할경우

}

 

아무 문제가 없을 꺼 같지만 아직 문제가 남아 있습니다. (B)의 주석을 제거 해보시면 에러가 나오는 것을 보실 수 있습니다. 왜 그럴까요 ? 컴파일러가 어떤 과정을 거쳐서 template을 인스턴스화(T를 해당 타입으로 치환하는 과정)하는지를 생각해 봅시다

 

(1)   먼저 template 자체의 문법이 맞는지 확인합니다.

(2)   T를 해당 타입으로 변경한 코드를 생성합니다.

(3)   다시 특정 타입으로 인스턴스화된 함수(클래스)의 문법이 맞는지 확인합니다.

(4)   코드 최적화 과정을 수행합니다.

 

이제 (A)의 경우와 (B)의 경우 컴파일러가 어떻게 Max()를 생성하는지를 생각해 봅시다.

먼저, A의 경우는 T int*로 변경됩니다. , 아래 처럼 Max가 생성됩니다.

const int*& Max( const int*& a, const int*& b )

{

        if ( true )

               return *a < *b ? b : a;

 

        return a < b ? b : a;

}

위 코드는 문법적으로는 아무 문제가 없습니다. 또한 최적화를 수행해서 마지막 문장은 제거해 버립니다.(if가 항상 true 이므로)

 

이번에 B의 경우 입니다. T int로 변경 됩니다. 아래 코드를 보세요..

 

const int& Max( const int& a, const int& b )

{

        if ( false )

               return *a < *b ? b : a; // error. int& 타입에대해*를수행할수없습니다.

 

        return a < b ? b : a;

}

문제가 되는 부분이 나왔군요.. a int& 타입인데.. *a를 수행하는 코드가 생성 되었군요최적화가 먼저 이루어져서 if 문을 컴파일 하지 않았다면 문제가 없을텐데, 현재 대부분의 C++ 컴파일러는 이 경우 에러를 발생 시키기 됩니다.

 

해결책이 없을까요 ? 자 이제, 해결을 위해서 2000년도에 C++ Report지에 게재 되었던 generic 프로그램의 1인자인 Andrei Alexandrescu( Modern C++의 저자)의 글을 소개해야 되겠군요.

 

4.    정수를 타입으로 int2type<>

 

Modern C++의 저자인 안드레이 알렉산드레스쿠는 2000년도에 C++Report지에 정수 값을 타입으로 변경해주는 int2type<> 이라는 template을 처음으로 소개 합니다. 그 구현은 아래와 같이 아주 단순하게 되어 있습니다.

template<int N> struct int2type

{

        enum { value = N };

};

 

template의 목적은 1, 2 등의 상수값 가지고  새로운 타입을 만들수 있게 됩니다. 장점은 상수값을 가지고 함수 오버로딩으로 사용할 수 있게 됩니다.

 

아래 코드를 보고 잘 생각해 보세요

 

void foo( int2type<0> a)

{

        cout << "int2type<0>" << endl;

}

 

void foo( int2type<1> a)

{

        cout << "int2type<1>" << endl;

}

 

int main()

{

        int2type<0> t0;

        int2type<1> t1;

 

        if ( typeid(t0) == typeid(t1))

               cout << "t0, t1 은동일타입입니다." << endl;

        else

               cout << "t0, t1 은다른타입입니다." << endl;

 

        // 이제정수상수0,1을가지고서로다른함수를호출할수있게되었습니다.

        foo(t0);

        foo(t1);

}

 

5.    Is_pointer<>, int2type<>, Max 의 결합

 

이제 3가지를 모두 결합해서 문제를 해결해 보도록 하겠습니다.

완성된 코드는 아래와 같습니다.

#include <iostream>

using namespace std;

 

template<typename T> struct is_pointer

{

        enum { value = false };

};

 

template<typename T> struct is_pointer<T*>

{

        enum { value = true };

};

 

template<typename T> struct is_pointer<const T*>

{

        enum { value = true };

};

 

template<int N> struct int2type

{

        enum { value = N };

};

 

// T가포인터가아닌경우의Max 구현입니다. - 3번째인자를조심해서보세요

template<typename T> const T& Max_impl(const T& a, const T& b, int2type<0> )

{

        return a < b ? b : a;

}

 

// T가포인터인경우

template<typename T> const T& Max_impl(const T& a, const T& b, int2type<1> )

{

        return *a < *b ? b : a;

}

 

// 실제Max는내부적으로는Max_impl을사용하게됩니다.

template<typename T> const T& Max( const T& a, const T& b )

{

        // bool 값상수를가지고타입으로변경한후인자로전달하기위해서익명의객체를생성합니다.

        return Max_impl( a, b, int2type<is_pointer<T>::value>());

}

 

int main()

{

        int x = 10;

        int y = 20;

 

        int n = Max(x, y);

        int* p = Max(&x, &y);

 

        cout << n << endl;

        cout << *p << endl;

}

 

 

,~ 힘드시죠.. 조금만 참으면 됩니다.

여기까지는 TR1의 내용은 아니고 TR1이 나오기 전에 사용되는 기법들입니다.

이제 TR1의 경우를 보도록 하지요..

 

6.    TR1 ? integral_constant

 

먼저 TR1에서 type_traits를 사용하려면 <type_traits> 헤더가 필요 합니다. 또한, TR1의 모든 요소는 std namespace 안에 tr1 이라는 namespace 안에 있습니다.

 

TR1이 제공하는 type traits의 가장 기본은 integral_constant<>입니다. int2type을 개선 한 것으로 생각할 수 있는데, 아래 처럼 사용하시면 됩니다.

#include <iostream>

#include <type_traits>

using namespace std;

using namespace std::tr1;

 

int main()

{

        integral_constant<int, 0> t0;

        integral_constant<int, 1> t1;

        integral_constant<bool, true> t2;

        //integral_constant<double, 3.2> t3; // error. 정수계열상수만가능합니다.

 

        if ( typeid(t0) == typeid(t1))

               cout << "같은타입" << endl;

        else

               cout << "다른타입" << endl;

}

실제 구현이 어떻게 되어있는지 살펴 보도록 하지요.(구현은 컴파일러 환경마다 다를수 있습니다. 아래 코드는 VC2008 featured pack을 설치한 경우 입니다. Dinkumware 사에서 만든 구현입니다.)

 

template<class _Ty,    _Ty _Val>

struct integral_constant

{

        static const _Ty value = _Val;

        typedef _Ty value_type;

        typedef integral_constant<_Ty, _Val> type;

};      

typedef integral_constant<bool, true> true_type;

typedef integral_constant<bool, false> false_type;

 

 

int2type<> int 형의 상수만을 사용했던 것에 비해서 integral_constant<>는 모든 정수 계열을 타입으로 변경할 수 있게 되었습니다. 또한, enum 상수 대신에 static const 멤버로 값을 보관하고 있으면 value_type type 이라는 typedef 2개를 추가 하고 있습니다.

 

이제, 중요한 것은 아래의 2개의 typedef입니다.

TR1 type_traits에서 아주 많이 사용하는 개념이니까, 확실하게 알아 두셔야 합니다.

 

bool 형태의 값 true false를 가지고 true_type, false_type을 만들고 있습니다. true, false는 둘 다 bool 이라는 동일 타입이지만 그것으로 만든 true_type false_type은 분명히 다른 타입 입니다.

 

7.    is_array<> 의 구현

 

T가 배열인지 아닌지를 알아내는 메타 함수를 TR1은 아래와 같이 제공하고 있습니다.

 

template<class _Ty> struct is_array : false_type

{      

};

 

template<class _Ty, size_t _Nx> struct is_array<_Ty[_Nx]> : true_type

{

};

 

template<class _Ty> struct is_array<_Ty[]> : true_type

{

};

 

primary template 버전과 부분 특화 버전 모두 true_type 또는 false_type으로 상속 받고 있기 때문에( 구조체 상속에서 접근변경자를 생략하면 기본적으로 public 상속입니다.) 모두 부모로부터 value 라는 static const 멤버를 상속 받기 때문에 아래 처럼 사용할 수 있습니다

 

#include <iostream>

#include <type_traits>

using namespace std;

using namespace std::tr1;

 

template<typename T> void test( const T& x)

{

        if ( is_array<T>::value )

               cout << "배열" << endl;

        else

               cout << "배열이아닙니다." << endl;

}

int main()

{

        int x[10];

        int n;

 

        test(x);

        test(n);

}

 

하지만, 여기서 재미 있는 사실은 기본 버전은 false 타입에서 상속 받고 부분 전문화 버전은 true type에서 상속되었다는 사실 입니다. 따라서 아래 처럼 오버로딩된 함수를 호출하는 용도로 사용하기가 편리 하게 되어 있습니다.

 

#include <iostream>

#include <type_traits>

using namespace std;

using namespace std::tr1;

 

void foo( true_type )

{

        cout << "배열" << endl;

}

void foo( false_type )

{

        cout <<"배열이아닌경우" << endl;

}

 

template<typename T> void test( const T& x)

{

        // is_array의임시객체를생성합니다.

        // T가배열이라면임시객체는true_type의자식입니다.

        // T가배열이아니라면임시객체는false_type의자식입니다.

        foo( is_array<T>());

}

 

int main()

{

        int x[10];

        int n;

 

        test(x);

        test(n);

}

 강석민강사님 데브피아에 쓰신 글 펌-

속은 비어있는데 할짓은 다하는 저런 깔끔한 생각은 대체
으음-_ -신기하다

      S T U D Y/C++  |  2008. 7. 7. 18:42



jekalmin's Blog is powered by Daum