C++11 부터는 auto 문법 덕분에 이 문제점들이 모두 사라졌다. auto 변수의 형식은 해당 초기치로부터 연역되므로, 반드시 초기치를 제공해야한다.
int x1; // 문맥에 따라서 초기화 되지 않을 수 있음
auto x2; // 오류 : 초기치가 꼭 필요함
auto x3 = 0; // 양호함 : x3의 값이 정의됨
그리고 auto는 형식 연역을 사용하므로 컴파일러에만 있던 형식을 지정할 수 있다.
auto derefUPLess =
[](const std::unique_ptr<Widget>& p1, // std::unique_ptr들이 가리키는 Widget
const std::unique_ptr<Widget>& p2) // 객체들을 비교하는 함수
{ return *p1 < *p2; };
auto derefLess =
[](const auto& p1, // C++14, 포인터처럼 작동하는 것들이
const auto& p2) // 가리키는 값들을 비교하는 함수
{ return *p1 < *p2; };
std::function은 C++11 표준 라이브러리의 한 템플릿으로, 함수 포인터 개념을 일반화 한 것이다. 단, 함수 포인터는 함수만 가리킬 수 있지만 std::function은 호출 가능한 객체이면 그 어떤 것도 가리킬 수 있다.
예를 들어 함수 서명에 해당하는 임의의 호출 가능 객체를 지정하는 std::function을 생성한다고 하자.
bool(const std::unique_ptr<Widget>&, // C++11 버전 std::unique_ptr<Widget> 비교 함수
const std::unique_ptr<Widget>&)
std::function<bool(const std::unique_ptr<Widget>&,
const std::unique_ptr<Widget>&)> func;
derefUPLess = [](const std::unique_ptr<Widget>& p1,
const std::unique_ptr<Widget>& p2)
{ return *p1 < *p2; };
auto와 std::function에서 외견상의 차이보다 더 중요한 차이가 있다.
auto로 선언된, 그리고 클로저를 담는 변수는 그 클로저와 같은 형식이며 그 클로저에 요구되는 만큼의 메모리만 사용한다. 하지만 std::function으로 선언된 변수의 형식은 std::function 템플릿의 한 인스턴스이며, 요구된 클로저를 저장하기에 부족할 수도 있고 그러면 힙 메모리를 할당해 클로저를 저장한다. 그렇기 때문에 std::function 객체는 auto로 선언된 객체보다 메모리를 더 많이 소비한다. 그리고 인라인화를 제한하고 간접 함수 호출을 산출하는 구현 세부사항 때문에, std::function 객체를 통해서 클로저를 호출하는 것은 거의 항상 auto로 선언된 객체를 통해 호출하는 것보다 느리다.
auto의 또 다른 장점은 type shourtcut이라고 부르는 것과 관련된 문제를 피할 수 있다는 것이다.
std::vector<int> v;
unsigned sz = v.size();
auto sz = v.size(); // sz의 형식은 std::vector<int>::size_type
v.size()의 공식적인 반환 형식은 std::vector<int>::size_type이다. Windows 32비트에서는 unsigned와 std::vector<int>::size_type은 같은 크기이지만 64비트 Windows에서는 unsigned가 32비트인 반면 std::vector<int>::size_type은 64비트이다. 하지만 auto를 사용하면 그 혼동을 줄일 수 있다.
또 하나의 예로
std::unordered_map<std::string, int> m;
for (const std::pair<std::string, int>& p : m)
{...}
std::unordered_map의 키부분이 const이기 때문에 std::pair의 형식은 std::pair<const std::string, int>이다. 그런데 이 형식은 루프문에 선언된 형식과 다르다. 그렇기 때문에 컴파일러는 std::pair<const std::string, int>를 std::pair<std::string, int>인 p에 해당하는 객체로 변환하려 한다. 각 반복에서 컴파일러는 p를 묶고자 하는 형식의 임시 객체를 생성하고 m의 각 객체를 복사하고 참조 p를 그 임시객체의 묶는 작업을 하게된다.
하지만 이런 문제 또한 auto를 사용함으로써 해결할 수 있다.
for (const auto& p: m)
{...}
게다가 p의 주소를 취하면 실제 m 안의 요소를 가리키는 포인터를 얻게 된다. auto를 사용하지 않은 코드에서는 임시 객체를 가리키는 포인터를 얻게 된다.
Conclusion
- auto 변수는 반드시 초기화 해야 하며, 이식성 또는 효율성 문제를 유발할 수 있는 형식 불일치가 발생하는 경우가 거의 없으며, 대체로 변수의 형식을 명시적으로 지정할 때 보다 경제적이다.
- auto로 형식을 지정한 변수는 연역 방식 때문에 문제를 겪을 수 있다.
Reference
Effective Modern C++ 항목 5: 명시적 형식 선언보다는 auto를 선호하라
'C++ > Effective Modern C++' 카테고리의 다른 글
객체 생성시 괄호와 중괄호의 구분 (0) | 2020.06.29 |
---|---|
auto가 원치 않은 형식으로 연역될 때에는 명시적 형식의 초기치를 사용 (0) | 2020.06.28 |
연역된 형식을 파악하는 방법 (0) | 2020.06.26 |
decltype의 작동 방식 (0) | 2020.06.26 |
auto 형식 연역 규칙 (0) | 2020.06.24 |
댓글