2017년 3월 2일 목요일

C++ type alias : 'using'

 C++11 이전의 C++ 개발자들이 자주 사용하던 using 키워드의 용도는 namespace를 계속 입력하는 게 귀찮아서 STL 사용 시 아래와 같은 선언을 코드의 앞 부분에 추가해주고 "std::"를 생략하기 위함일 것이다. (내 경우에는 그랬다)
using namespace std;
...
 C++11에서 using의 기능을 새롭게 추가하였는데 그것이 타입 별칭(Type alias)을 선언하는 것이다. 기존에 typedef라는 키워드를 이용해서 STL 등에서 제공하는 긴 iterator 타입을 간단하게 선언해서 사용해 본 경험이 많이들 있을 거라고 생각한다. using을 이용한 타입 별칭 선언은 typedef가 하는 일과 마찬가지로 기존 타입을 새로운 이름으로 선언하는 것이다. 아래 함수 포인터 타입을 선언하는 2가지 예를 보면서 어떤 게 더 보기 좋은지 한번 생각해보자.
typedef void (*FP)(int, const std::string&);  // typedef 이용
using FP = void (*)(int, const std::string&); // using 이용
 다른 건 몰라도 함수 포인터 타입을 선언할 때는 using이 훨씬 분명하게 뜻이 전달되는 것 같다. 하지만 이것만이 기존 typedef를 사용하던 습관을 버리고 using을 사용해야 할 이유라면 아마도 typedef가 익숙한 개발자에게는 별로 납득할만한 이유는 되지 못할 것 같다. Effective Modern C++책 에서는 typedef보다는 using을 사용하라고 권장하며 그 이유에 대해서도 설명하고 있는데 여기서 그 이유에 대해 정리를 해 보려고 한다.

 using을 이용한 타입 선언이 typedef와 크게 구별되는 점이 하나 있다. using의 경우에는 템플릿화가 되지만 typedef는 그렇지 못하다는 점이다. 아래 예를 보자.
template<typename T> // std::list<T, MyAlloc<T>>의 별칭은 MyAllocList<T>
using MyAllocList = std::list<T, MyAlloc<T>>;

MyAllocList<Widget> lw; // client code
 C++11에서는 이렇게 using을 이용해서 간단히 T에 따라 변화하는 템플릿 타입의 별칭을 선언할 수 있지만 기존 typedef의 경우에는 템플릿화할 수 없기 때문에 이러한 목적을 이루기 위해서 아래 예와 같은 방법을 사용하였다.
template<typename T> // std::list<T, MyAlloc<T>>의 별칭은 MyAllocList<T>::type
struct MyAllocList {
  typedef std::list<T, MyAlloc<T>> type;
};

MyAllocList<Widget>::type lw; // client code
 템플릿 구조체를 하나 선언해서 그 안에서 typedef를 이용하여 별칭을 선언하는 것이다. 이렇게 함으로써 실제 타입을 사용할 때 "::type"을 덧붙인다는 소소한 불편함을 제외하면 같은 동작을 하도록 할 수 있었다. 하지만 이렇게 선언된 타입은 템플릿 안에서 사용할 때 또 다른 불편함이 발생한다. 아래 예를 보자.
template<typename T>
class Widget {
private:
  typename MyAllocList<T>::type list; // MyAllocList<T>를 멤버로 선언
  ...
};
 MyAllocList::type 타입의 멤버를 선언하는 데 그 앞에 typename이라는 키워드를 써주어야 하는 불편함이 생긴다. 컴파일러가 MyAllocList::type을 만났을 때 이게 타입 이름인지 아니면 MyAllocList의 데이터 멤버인지 알 수가 없기 때문에 typename이라고 앞에 지정함으로써 타입 이름이라고 알려주는 것이다. using을 이용한 타입 별칭 선언은 항상 타입이라는 게 확실하기때문에 아래 예에서 보듯이 typename을 따로 지정하지 않는다.

template<typename T>
using MyAllocList = std::list<T, MyAlloc<T>>;

template<typename T>
class Widget {
private:
  MyAllocList<T> list;// "typename", "::type"이 없다.
  ...
};

 참고로 한 가지 더 정리하자면 using을 이용한 타입의 별칭 선언은 C++11에서 추가되었지만 STL의 많은 코드는 이전과의 호환성 때문에 이전 방식인 구조체 안에 type을 선언하는 방식을 그대로 유지하고 있다. 하지만 C++14에서는 이전보다 더 간단하게 사용할 수 있도록 같은 기능을 제공하는 템플릿 선언들을 추가로 제공한다. 아래 예에서 보듯이 기존 구조체 이름에 '_t'를 붙여서 같은 기능을 제공하도록 선언하였다.
std::remove_const<T>::type // C++11: const T → T
std::remove_const_t<T>     // C++14 버전

std::remove_reference<T>::type // C++11: T&/T&& → T
std::remove_reference_t<T>     // C++14 버전

std::add_lvalue_reference<T>::type // C++11: T → T&
std::add_lvalue_reference_t<T>     // C++14 버전
 위 예는 헤더파일에서 제공하는 선언들이다. 새롭게 추가된 선언들의 이름이 무엇인지 어렵지 않게 예상할 수 있을 것으로 생각된다.

0 개의 댓글:

댓글 쓰기