traits #2

Tags:

1탄은 traits 를 참고.

Traits의 가장 큰 특성은, non-intrusive하다는 점입니다. 즉, 어떤 타입 정보를 전달함에 있어서 기존의 코드를 최소한으로 깬다는 것이죠. 예를들어 A라는 클래스는 tag1의 속성을, B라는 클래스는 tag2의 속성을 갖는다고 합시다. 그러면 다음과 같이 tag를 정의할 수 있을 것입니다.

struct tag1 { };
struct tag2 { };

...

struct A
{
    typedef tag1 type;
};

struct B
{
    typedef tag2 type;
};

그러면 우리는 A의 속성은 A::type으로, B의 속성은 B::type으로 접근가능하겠죠. 이런 것이 왜 필요하냐고 의문을 품을 수 있겠지만, 이런 것이 필요한 경우는 많습니다. 한 예가 iter_swap example인데요. 해당 예에서는 vector에 저장된 타입이 int일까? string일까? 같은 것을 결정하는데 있어 다음과 같은 식의 코드를 씁니다.

vector<string> vs;
iterator_traits<vs>::value_type

그럼 필요성은 확인이 된거니, 다시 앞의 예로 돌아가죠. 우리는 A와 B의 타입 정보를 이제 압니다. 그런데 이 시스템에 우리가 못건드는 C라는 클래스나 혹은 ‘int’ 라는 primitive 에 대해서 속성을 정하고 싶다면 무척 난감하겠죠. 이 경우에 traits를 사용해 해결할 수 있습니다. 먼저, 일반적인 용도의 traits를 정의합니다.

template <class T>
struct traits
{
    typedef typename T::type type;
};

이제 속성 정보가 필요하다면 traits<A>::type 이나 traits<B>::type 과 같이 접근할 수 있습니다. 다음, 우리가 손을 못대는 경우에는 다음과 같이 specialization시킵니다.

template <>
struct traits<int>
{
    typedef tag1 type;
};

멋지죠? 그러면 이제 int 에 대해서는 traits<int>::type 로 접근하면되고, 이것은 A, B, int에 대해 동일한 인터페이스가 가능함을 뜻합니다. 전체를 포괄하는 예를 보죠.

#include

using namespace std;

struct tag1 { };
struct tag2 { };

ostream& operator<<(ostream& os, const tag1&) { os << "tag1" << endl; return os; } ostream& operator<<(ostream& os, tag2) { os << "tag2" << endl; return os; } struct A { typedef tag1 type; }; struct B { typedef tag2 type; }; template
struct traits
{
typedef typename T::type type;
};

template <>
struct traits
{
typedef tag1 type;
};

int main()
{
cout << "traits::type=” << traits::type() << endl; cout << "traits::type=” << traits::type() << endl; cout << "traits::type=” << traits::type() << endl; return EXIT_SUCCESS; } [/code] 마치기 전에, 한가지 주의할 점이 있습니다. 코드에서 보면 tag1과 tag2에 대한 operator<< 의 overloading이 각각 다릅니다. 첫번째는 const tag1& 로 받았고, 두번째는 tag2 로 받았습니다. 그 이유는 여기에 넘겨지는 인자는 traits<A>::type() 의 예에서와 같이 임시적으로 생성되기 때문입니다. 이 객체가 임시 객체이므로 곧바로 파괴되야하고 따라서 operator<< 에서 이를 아무생각없이 reference로 받으면 안됩니다. 즉, 임시적으로 스택에 만들어졌다가 파괴되는 객체를 절대로 reference로 받아서는 안됩니다. 이것이 중요한 또다른 이유는 컴파일러가 무척이나 cryptic 한 에러메시지를 내보내기 때문에, 이 사실을 모르면 에러를 절대 못잡기 때문입니다.

따라서 해당 객체들은 const T& 로 받거나, T로 값에 의한 복사를 해야합니다. 이 때, 한가지 또 궁금증은 왜 T&는 안되고, const T&는 될까겠죠. 그 이유는, 스택에 만들었다가 곧 파괴할 객체는 참조로 받아서 값을 변형하는 것은 무의미하기 때문에 참조로 받는 것을 금하고, const 의 경우엔 참조로 받더라도 수정이 불가하므로 허가한다고 생각하면 됩니다.