관리 메뉴

KoreanFoodie's Study

Modern Effective C++ 정리 2 : auto 본문

Tutorials/C++ : Advanced

Modern Effective C++ 정리 2 : auto

머니덕 2022. 9. 20. 12:20

C++ 프로그래머의 필독서이자 바이블인, 스콧 마이어스의 Modern Effective C++ 를 읽고 기억할 내용을 요약하고 있습니다. 꼭 읽어보시길 추천드립니다!


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

핵심 :

1. auto 변수는 반드시 초기화 해야 하며, 이식성 또는 효율성 문제를 유발할 수 있는 형식 불일치가 발생하는 경우가 거의 없으며, 대체로 변수의 형식을 명시적으로 지정할 때보다 타자량도 더 적다.
2. auto 로 형식을 지정한 변수는 항목 2 와 항목 6 에서 설명한 문제점들을 겪을 수 있다.

 

아래 예시를 보면, auto 를 쓰는 것이 여러모로 훌륭하다는 것을 한눈에 파악할 수 있을 것이다.

template<typename It>
void print_from_b_to_e(It b, It e)
{
	for (; b != e; ++b)
	{
		typename std::iterator_traits<It>::value_type
			currValue = *b;
		std::cout << currValue << std::endl;

		// auto 를 쓰면 훨씬 간단하다!
		auto currValue2 = *b;
		std::cout << currValue2 << std::endl;
	}
}

 

또한, auto 는 예전에는 컴파일러만 알던 형식을 지정할 수도 있고, 람다 표현식의 매개변수에도 사용할 수 있다.

std::function<bool(const std::unique_ptr<Widget>& p1,
	const std::unique_ptr<Widget>& p2)>
 derefUPLess_bad =
	[] (const std::unique_ptr<Widget>& p1,
			const std::unique_ptr<Widget>& p2)
	{ return *p1 < *p2; };


// std::unique_ptr 들이 가리키는 Widget 객체들을 비교
auto derefUPLess =
	[] (const std::unique_ptr<Widget>& p1,
			const std::unique_ptr<Widget>& p2)
	{ return *p1 < *p2; };

// C++14 버전 :
// 그 어떤 것이든 포인터처럼 작동하는 것들이 가리키는 값들을 비교
auto derefUPLess2 =
	[] (const auto& p1,
			const auto& p2)
	{ return *p1 < *p2; }	;

 

또한 auto 는 "형식 단축(type shortcut)" 과 관련된 문제를 피할 수 있다. 예시를 보자.

std::vector<int> v;
// v.size() 의 공식적인 반환 형식은 std::vector<int>::size_type 임
unsigned sz = v.size();
// 하지만 64 비트 Windows 에서는 unsinged 가 32 비트인 반면,
// std::vector<int>::size_type 은 64 비트로, 오작동 가능성이 있다!

// 이식성 문제 해결
auto sz2 = v.size();

이는 이식성과도 연관이 된다.

 

마지막으로, auto 를 사용하지 않으면 오히려 안되는 경우를 보자.

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

위 코드에서, 키 부분에 const 가 붙었으므로, 해시 테이블에 담긴 std::pair 의 형식은 std::pair<std::string, int> 가 아니라 std::pair<const std::string, int> 이다. 컴파일러는 std::pair<std::string, int> 객체를 std::pair<const std::string, int> 객체들로 변환하려 든다. 각 반복에서 컴파일러는 p 형식의 임시 객체를 생성하고, m 의 각 객체를 복사하고, 참조 p 를 그 임시 객체에 묶음으로써 그러한 변환을 실제로 수행한다. 그 임시 객체는 루프 반복의 끝에서 파괴된다.

auto 를 사용하면 어떨까?

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

p 의 주소는 실제로 m 안의 한 요소를 가리키는 포인터의 주소이다. 이는 효율적이고 타자도 더 쉽다.

 

 

항목 6 : auto 가 원치 않은 형식으로 연역될 때는 명시적 형식의 초기치를 사용하라

핵심 :

1. "보이지 않는" 대리자 형식 때문에 auto 가 초기화 표현식의 형식을 "잘못" 연역할 수 있다.
2. 형식 명시 초기치 관용구는 auto 가 원하는 형식을 연역하도록 강제한다.

 

다음 예시를 보자.

class Widget {};

std::vector<bool> feature(const Widget& w)
{
	return std::vector<bool>(10);
}

void processWidget(Widget w, bool highPriority) { /* Do something */ }

int main()
{
	Widget w;
	bool highPriority = feature(w)[5];
	processWidget(w, highPriority);

	auto highPriority2 = feature(w)[5];
	// 미정의 동작
	processWidget(w, highPriority2);
}

std::vector<bool> 의 operator [ ] 는 요소에 대한 참조(bool 을 제외한 모든 형식에 대해서는 std::vector::operator[ ] 가 참조를 돌려줌)가 아닌, std::vector<bool>::reference 형식의 객체를 돌려준다. 그 이유는, std::vector<bool> 이 자신의 bool 들을 bool 당 1 비트의 압축된 형태로 표현하도록 명시되어 있기 때문이다. C++ 는 비트에 대한 참조는 금지되어 있어서, std::vector<bool>::reference 를 bool 로 암묵적으로 변환하는 방식을 채택한다.

operator [ ] 가 std::vector<bool>::reference 를 반환하는데, 이는 보통 참조된 비트를 담은 기계어 워드(word) 를 가리키는 포인터 하나와 오프셋으로 구성되어 있다. 명시적으로 bool 로 선언한 경우, 해당 오프셋을 읽은 후 그 값이 바로 bool 타입으로 변환되어 저장된다. 그런데 auto 는 지금 포인터와 오프셋을 담은 객체로 연역된 상태이다. 추가로 bool 로 암시적 변환이 되는 게 아니란 뜻이다.

그런데 feature 함수는 std::vector<bool> 타입의 임시 객체(temp 라고 부르겠다)를 돌려준다. 그런데 temp 는 임시 객체이므로 문장의 끝에서 파괴된다. 따라서 highPriority 의 포인터는 대상을 잃은 포인터(dangling pointer) 가 되어 processWidget(w, highPriority2) 는 미정의 동작을 보이는 것이다.

std::vector<bool>::reference 는 대리자 클래스(proxy class), 즉 다른 어떤 형식의 행동을 흉내 내고 보강하는 것이 존재 이유인 클래스의 예이다. 그리고 대체로 이러한 "보이지 않는" 대리자 클래스는 auto 와 잘 맞지 않는 경향이 있다! 따라서, 다음과 같은 형태의 코드는 피해야 한다.

auto someVar = "보이지 않는" 대리자 클래스 형식의 표현식;

// "보이지 않는" 대리자 클래스 형식의 표현식 구현 예시
// std::vector<bool>::operator[ ] 의 명세
namespace std
{
	template<class Allocator>
	class vector<bool, Allocator>
	{
	public:
		class reference { ... };

		reference operator[](size_type n);
		...
	};
}

 

하지만 대리자 클래스를 쓰는 라이브러리를 사용한다고 해서 auto 를 아예 안 써야 하는 건 아니다. 다음과 같이 형식 명시 초기치 관용구(explicitly typed initializer idiom)를 사용하면 된다.

auto highPriority2 = static_cast<bool>(feature(w)[5]);

 

0 Comments
댓글쓰기 폼