[Modern C++] 항목 5: 명시적 형식 선언보다는 auto를 선호하라

게시:     수정

카테고리:

태그: ,

이 글은 아래의 책을 정리하였습니다. 이펙티브 모던 C++, 스콧 마이어스 저자, 류광 번역

📦 2. auto

👉🏻 항목 5: 명시적 형식 선언보다는 auto를 선호하라

1️⃣ 변수 초기화

// 명시적 형식 선언
int x1; // 문맥에 따라 초기화되지 않을 수 있음

// auto 사용
auto x2; // ⚠️ 에러! 초기치 필요!
auto x3 = 0; // ✅ x3 정의됨
  • auto를 사용하면, 초기화로 인한 잠재적 문제가 사라진다.

2️⃣ 변수 선언

template<typename It>
void dwim(It b, It e) {
	for(; b != e; ++b) {
		// 명시적 형식 선언
		typename std::iterator_traits<It>::value_type
			currValue = *b;
		
		// auto 사용
		auto currValue = *b;
		...
	}
}
  • 명시적 선언의 경우, 변수 선언에 많은 수고가 든다.

3️⃣ 클로저 사용

// 명시적 형식 선언
???? derefUPLess =
	[](const std::unique_ptr<Widget>& p1,
		 const std::unique_ptr<Widget>& p2)
	{ return *p1 < *p2; };

// auto 사용: C++14 이전
auto derefUPLess =
	[](const std::unique_ptr<Widget>& p1,
		 const std::unique_ptr<Widget>& p2)
	{ return *p1 < *p2; };

// auto 사용: C++14
auto derefUPLess =
	[](const auto& p1,
		 const auto& p2)
	{ return *p1 < *p2; };
  • 클로저: 람다 표현식을 통해 생성되는 익명 함수 객체
  • 명시적 선언의 경우, 클로저 형식으로 변수 선언이 불가능하다.
    • 클로저 형식은 컴파일러만 알고 있기 때문이다.
  • auto를 사용하지 않기 위해 function을 사용한다 하더라도, 문제가 생긴다.
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: 클로저를 담는 변수 형식 = 클로저 형식이며, 필요한 만큼의 메모리만 사용한다.
  • function: function 변수 형식 = function 템플릿의 인스턴스이며, 주어진 시그니처에 따라 메모리 크기가 고정되어 있다.
    • 메모리가 부족하면, 힙 메모리를 할당하여 사용한다.
    • 메모리 부족 예외가 발생할 수도 있다.

느린 속도:

  • function: 인라인화를 제한하고, 간접 함수 호출을 산출하는 구현 세부 사항이 존재한다.
    • 이로 인해, auto보다 느리다.

4️⃣ 형식 단축

vector<int> v;

// v.size() 반환형은 vector<int>::size_type
unsigned sz = v.size();
auto sz = v.size();
  • v.size()의 반환형은 vector<int>::size_type이며, 부호 없는 정수형이다.

문제점:

자료형 / 운영체제 32비트 Windows 64비트 Windows
unsigned 32비트 32비트
vector<int>::size_type 32비트 64비트
  • 위 문제로 인해, 64비트 운영체제에서 오작동을 할 수 있다.
unordered_map<string, int> m;

// ⚠️ 비효율적임!
for(const pair<string, int>& p : m) {
	...
}

// ✅
for(const auto& p : m) {
	...
}
  • unordered_mapKey 부분은 const다.
    • 즉, pair<const string, int>가 되어야 하며, 이로 인해 문제가 발생한다.
  • 컴파일러는 pair<const string, int>pair<string, int>로 변환하려 한다.
    1. p 형식의 임시 객체 temp 생성
    2. mtemp, 각 객체 복사
    3. ptemp, 객체 묶기
    4. 루프 끝에서, temp 파괴
  • 의도치 않은 변환이 발생할 수 있다.

💡 추가 내용

  • 형식 추론을 채용하는 다른 언어들이 있으며, 대규모 코드 작성 및 유지 보수에 문제가 되지 않는다.
  • 객체의 형식을 파악하는 데 큰 문제가 되지 않는다.
    • IDE를 통한 객체 형식 파악, 변수 이름 등을 통해 충분히 파악할 수 있다.
  • auto를 사용하면, 초기화 표현식이 변하더라도 코드를 수정할 일이 없다.
    • 즉, 리팩터링이 수월해진다. (e.g. intlong으로 전환하는 경우)

🧐 정리

  • auto 변수는 반드시 초기화해야 한다.
  • 이식성/효율성 문제를 유발할 수 있는 형식 불일치가 발생하는 경우가 거의 없다.
  • 명시적 형식 선언보다 타자량이 적어진다.
  • auto로 형식을 지정하면, 항목 2와 항목 6에서 설명한 문제점을 겪을 수 있다.

Cpp 카테고리 내 다른 글 보러가기

댓글남기기