본문 바로가기
컴퓨터/Modern Effective C++ 정리

Modern Effective C++ Item 5 : auto는 우월하다

by 김짱쌤 2015. 3. 10.

Item 5 auto는 우월하다.

auto의 개나소나 알 수 있는 장점

일반 타입은 초기화 하지 않은 변수를 허용하므로 분명하게 정의되지 않는 경우가 있다.
반면 auto는 초기화 하지 않고서 사용할 수 없다.

   int x1;      //초기화 되지 않은 상태로 작동
   auto x2;     //컴파일 에러
   auto x3 = 0; //x3이 잘 정의된 상태로 작동

타입명이 지나치게 긴 경우 가독성을 해친다.
이 경우 auto를 사용하여 간결하게 표현할 수 있다.
C++14의 경우 람다의 매개 변수 타입에 auto를 사용할 수 있다.

   std::function<bool(const std::unique_ptr<Widget>&,
                      const std::unique_ptr<Widget>&)>
        derefUPLess = [](const std::unique_ptr<Widget>& p1,
                         const std::unique_ptr<Widget>& p2)
                       { return *p1 < *p2; };             //엄청나게 긴 선언

   auto derefUPLess = [](const std::unique_ptr<Widget>& p1,
                         const std::unique_ptr<Widget>& p2)
                       { return *p1 < *p2; };             //타입을 auto로 변경 간결한 선언

   auto derefUPLess = [](const auto& p1, const auto& p2)
                       { return *p1 < *p2; };           //C++14부터 람다 인자 auto를 활용한 훨씬 간결한 선언

+@ std::function?

일반화된 함수 포인터 템플릿.
함수 포인터와 다르게 모든 callable object들(함수객체, 람다, std::bind...)을 가리킬 수 있다.
함수 signature에 따른 템플릿으로 동작한다. *detail
위의 예제 derefUPLess 람다 함수의 경우

   bool(const std::unique_ptr<Widget>&,
        const std::unique_ptr<Widget>&) //derefUPLess의 signature

   std::function<bool(const std::unique_ptr<Widget>&,
                      const std::unique_ptr<Widget>&)> derefUPLess //템플릿 선언

+@ Closure?

책에서 함수를 가리킬때 계속되서 언급되는 개념.
Link : wikiblog
1줄 요약하자면 상태를 갖고 있는 함수 객체, capture를 포함한 람다도 일종의 closure
책에서는 인자를 포함한 함수 인스턴스를 closure라고 이야기하는 듯 하다.

void Closure()
{
  int state = 5;

  // state 값을 pass by value로 capture 하였다.
  // 람다식을 auto f 변수에 대입하였다.
  auto f = [state](int param)
  {
    auto result = state + param;
    std::cout << result << std::endl;
  };
  // f는 closure
  // 람다식 실행.
  f(10);
}

auto 와 std::function의 차이점

  • 변수가 차지하는 메모리 용량
    std::function은 모든 종류의 함수 객체를 담을 수 있는 템플릿.
    모든 함수 객체를 가르킬 수 있어야하기 때문에, 모든 함수형 객체에게 필요한 메모리 공간을 마련해 둔다. 따라서 std::function템플릿 클래스는 구체적인 함수의 클로저(타입)보다 더 많은 메모리를 소비한다.

  • 함수 호출 수행시간 여러 함수 객체의 호출 방식이 서로 다르기 때문에 std::function 템플릿 클래스인 함수객체를 호출하는 시간이
    구체적인 함수의 클로저를 호출하는 시간보다 오래걸린다.

  • auto를 쓰면
    해당 함수객체의 구체적인 타입으로 추론하여 처리하기 때문에
    저장공간과 호출 수행시간하는데 있어서 더 효과적이다.

auto는 포팅을 쉽게한다.

32bit/ 64bit에 따라서 크기가 달라지는 타입의 경우,
일반 primitive type으로 받으면 포팅할때 번거롭다.
이때 auto를 사용하여 해당 타입으로 추론하여 처리하면 포팅할때 간편하게 처리 가능하다.

  std::vector<int> v;
  unsigned sz1 = v.size(); // vector<T>.size()의 리턴형식은 std::vector<int>::size_type (32/64bit)
                           // unsigned의 경우 항상 32비트 타입이므로 64bit환경에서 이 코드는 잘 동작하지 않는다.
  auto sz2 = v.size();     // auto를 활용하여 타입 추론하면 문제없이 해결!

명시적 선언과 그 실제 활용에서의 형태가 다른 경우 auto를 사용하자

그냥 말로 하면 무슨 소리인지 알기 힘들다. 예제를 주목하자.

   std::unordered_map<std::string, int> m;
   …
   for (std::pair<std::string, int>& p : m)
   {
   … // do something with p
   }

std::unordered_map의 key는 항상 const이다.
따라서 템플릿 인자로 std::string, int를 넣는다 해도
실제 만들어지는 pair의 형태는 std::pair<const std::string, int>가 된다.
이 부분이 직관적으로 기대하기 어렵기 때문에 자주 실수하기 쉽다.
실제로 위처럼 사용하면, m의 이터레이터의 내용물과 p의 타입이 다르므로
p는 복사된 임시 값으로 실제 m의 원소들과 관계없이 처리된다.
이때 auto를 사용하면 모든 문제가 해결된다.

리팩토링시에도 auto로 받아두면 좋다.

리팩토링시 함수의 리턴 타입이 바뀐다면,
해당 함수와 관계없는 호출 영역에서 리턴한 값을 받는 타입을 조정해주어야 한다. 하지만 리턴한 값을 받는 타입을 auto로 지정하면, 호출 함수의 리턴형식이 달라져도 문제없이 동작한다. 따라서 auto를 사용하면 리팩토링할때 쓸데없는 변경을 하지 않아도 된다.