Post List

2015년 4월 28일 화요일

item 09: typedef 보다 별칭선언(using)을 사용하자. (MVA Version)

작가
Meyers, Scott
출판
O'ReillyMedia
발매
2014.12.12
평점








Effective Modern C++ - Scott Meyers
Item 9: Prefer alias declarations to typedefs.

Item 9. typedef 보다 별칭 선언을 사용하자.

Agenda

* C++98 typedef,  #define
* C++11 using (alias declaration)
* alias template
* Type Trait Transformation

* C++98 typedef

C++에서 타입의 길이가 긴 경우 줄여서 사용하는 경우가 많습니다.
C++98에서는 #define을 이용한 방법과 typedef를 주로 이용했습니다.

#define INT32               int
typedef unsigned char       BYTE;
typedef unsigned short      WORD;

#define은 단순히 컴파일 타임에 소스에서 문자열을 치환해주는 기능으로,
나중에 디버깅이 힘들어지므로 어느 누구나 다 쓰지말라고 할 것입니다.

물론 사용해서 편리한 경우도 있습니다.
(통상적으로 #define을 이용하여 함수처럼 만들어 사용하는 것을 매크로 라고 합니다.)
매크로의 경우에는 ## (token 연결 연산 : token pasting operator)가 있어서, 변수나 함수명을 만들 수가 있습니다.
C++은 아직 Reflection이 지원되지 않는 언어라서 매크로를 안쓰고는 이렇게 못합니다.

int nVal1 = 2;
int nVal2 = 3;

void FUNC_SKY(int args) { std::cout << "Sky : " << args << std::endl; }
void FUNC_FLY(int args) { std::cout << "Fly : " << args << std::endl; }

#define MAC_FUNC(F, N) FUNC_##F(nVal##N)

MAC_FUNC(SKY, 2); // Sky : 3

하지만, typedef은 많이들 사용하십니다.
본인은 사용안한다고 하시는 분들도 분명 있으시겠죠 ?
저렇게 쓰면 원래 타입을 바로 알수가 없어서 코드 이해도를 떨어트린다고 쓰지말란 분도 계십니다.
그러면 기분들은 과연 std::string을 원래 형태대로 다 적으시는지 안궁금해질수가 없네요.

typedef basic_string<char, char_traits<char>, allocator<char>> string;

항상 저렇게 std::basic_string<char, char_traits<char>, allocator<char>> 로 당연히 사용하시겠죠 ?
사람이 한입으로 두말하면 안되자나요.

const std::_Simple_types<std::_Wrap_alloc<std::_Vec_base_types<Widget, std::allocator<Widget> >::_Alloc>::value_type>::value_type *

item 4 33페이지에 나와있는 타입입니다.
과연 읽으라고 만들어 놓은 타입일까요 ?
STL의 경우는 참... 타입의 길이가 해도해도 너무한 경우가 많습니다.

* C++11 alias declaration ( using )

C++ 11에는 별칭 선언 (alias declaration) 이라는 새로운 방법이 생겼습니다.

typedef std::unique_ptr<std::unordered_map<std::string, std::string>> UPM_SS// C++98 typedef

using UPM_SS = std::unique_ptr<std::unordered_map<std::string, std::string>>; // C++11 using

C++98 스타일의 typedef와 차이가 별로 없어 보입니다.
그냥 타입명 UPM_SS이 앞에 가그냐 뒤에 가느냐 그 차이뿐이랄까요 ?
과연 뭐 더 깔끔해졌는지 잘 모르겠습니다.

이제 Function Pointer에 적용시켜 보겠습니다.

typedef void(*Func)(int, const std::string&); // typedef
using Func = void(*)(int, const std::string&);  // using (alias declaration)

타입명 Func이 중간에 가는 것보다는 앞으로 나와서 훨씬 더 눈에 잘 들어옵니다.

하지만 지금까지 본건 여전히 보기 좋아졌다 정도지, 새로운 기능이라고는 내세울 만한게 없습니다.

* alias template

이제부터 typedef로는 할 수 없었던 기능을 가능하게 해준 것에 대해 보겠습니다.
바로 template화가 가능해졌습니다. ( alias template )
C++98에서도 template struct와 typedef를 같이 사용하는 꼼수가 있었습니다.

template <class T>
class MyAlloc {};

template<typename T>                       // using (alias declaration)
using MyList = std::list<T, MyAlloc<T>>;

template<typename T>                       // typedef
struct MyList_
{
typedef std::list<T, MyAlloc<T>> TYPE;
};

그냥 typedef를 사용하는 것이 선언 할때만 저렇게 불편할까요 ?
사용할때의 불편함은 더합니다.

MyList<Widget> LW;
MyList_<Widget>::TYPE LW_;

사용할때마다 ::TYPE 같은걸 항상 붙여줘야 합니다.
컴파일러가 typedef는 typename T에 의존적인 타입이라서 타입명 ::TYPE 를 붙여줘야 하지만,
using의 경우는 MyList<T자체를 타입으로 인식합니다. (T에 의존적인 타입이 아니란 뜻)

* Type Trait Transformation

Template Meta Programming (TMP)를 경험한 적이 있는 개발자라면,
Type Trait에 대해서 들어봤을 것입니다.
Effective C++에 자세히 설명이 나옵니다.
(필자도 그걸 읽고, Blog에 정리까지 하긴 했지만... 두번 다시 생각하기 싫은 내용인지라...)

그 중 template 인자 타입의 속성을 변환하여 사용하는 것을 C++11에서 재공해 주었습니다.
Type Trait Transformation )
사용법은 아래와 같습니다.

std::remove_const<T>::type          // T from const T
std::remove_reference<T>::type      // T from T&, T&&
std::add_lvalue_reference<T>::type // T& from T

모두 뒤에 ::type이 붙어있습니다.
딱봐도 template struct와 typedef를 같이 사용했다는 것을 알 수 있겠죠 ?
C++14에서는 using을 사용한 것을 제공해줍니다. 뒤에 _t를 붙여주면 됩니다.

std::remove_const_t<T>          // T from const T
std::remove_reference_t<T>      // T from T&, T&&
std::add_lvalue_reference_t<T> // T& from T

예전에 만들어놓은 typedef를 using로 새로 정의하는 방법은 아래를 참조하시면 됩니다.

template<class T>
using remove_const_t         = typename std::remove_const<T>::type;

template<class T>
using remove_refernece_t     = typename std::remove_reference_t<T>::type;

template<class T>
using add_lvalue_reference_t = typename std::add_lvalue_reference<T>::type;

Things to Remember

typedef는 template를 지원하지 않지만, using (별칭 선언)은 가능하다.

using 은 ::type을 뒤에 붙일 필요도 없고, typename 을 앞에 붙일 필요도 없다.

* C++14 에서는 C++11 의 Type Trait Transformation를 별칭 선언으로 지원한다.

댓글 없음:

댓글 쓰기