일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
Tags
- 영화 리뷰
- 암시적 변환
- 티스토리챌린지
- lua
- UE4
- 게임
- more effective c++
- 상속
- operator new
- resource management class
- 언리얼
- 스마트 포인터
- virtual function
- exception
- effective stl
- Smart Pointer
- 비교 함수 객체
- 다형성
- reference
- 함수 객체
- Effective c++
- implicit conversion
- 반복자
- 메타테이블
- 영화
- c++
- 오블완
- 예외
- effective modern c++
- 참조자
Archives
- Today
- Total
스토리텔링 개발자
[Effective Modern C++] 5. 타입 명시보다 auto 본문
728x90
항목 5. 명시적 타입 선언보다는 auto를 선호하라
auto를 사용하는게 좋은 상황들
// 1.
int x;
// 깜빡 잊고 초기화하지 않았다!
// 2.
template<typename It>
void dwim(It b, It e)
{
for(; b != e; ++b)
{
typename std::iterator_traits<It>::value_type currValue = *b;
...
}
}
// 반복자가 가리키는 값의 타입이 지나치게 길다.
// 3.
// 클로저의 타입으로 지역 변수를 선언해보자.
// 클로저의 타입은 컴파일러만 알고 있으므로 명시적으로 지정하는 것은 애초에 불가능하다..
해결
// 1.
int x1; // 문맥에 따라 초기화되지 않을 수 있다.
auto x2; // 컴파일 에러
auto x3 = 0; // 초기화를 반드시 챙길 수 있다.
// 2.
template<typename It>
void dwim(It b, It e)
{
for (; b != e; ++b)
{
auto currValue = *b; // 긴~ 타입을 생략할 수 있다.
...
}
}
// 3.
// auto는 타입 추론을 사용한다.(항목 2 참조)
// 그러므로 컴파일러만 알던 타입을 지정할 수 있다.
auto derefUPLess = [](const std::unique_ptr<Widget>& p1,
const std::unique_ptr<Widget>& p2)
{
return *p1 < *p2;
};
// C++14에서는 람다 표현식 매개변수에도 auto를 적용할 수 있다.
auto derefUPLess = [](const auto& p1,
const auto& p2)
{
return *p1 < *p2;
};
클로저를 담을 땐 std::function 객체를 써도 되지 않을까?
- std::function
- 함수 포인터 개념을 일반화한, C++11 표준 라이브러리의 템플릿 중 하나이다.
- 다만 함수 포인터는 함수만 가리킬 수 있지만, 이 템플릿은 함수처럼 호출할 수 있는 객체면 무엇이든 가리킬 수 있다.
- 선언 시, 담을 함수의 형식을 반드시 지정해야 한다.
-
std::function<bool(const std::unique_ptr<Widget>&, const std::unique_ptr<Widget>&)> func;
- 그럼 이제 위의 예시를 auto 대신 std::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;
};
- std::function의 문제점
- 예시를 보면 알 수 있듯, 매우 장황해진다.
- 대체로 std::function은 auto보다 메모리를 더 많이 소비한다.
- auto로 선언된 클로저를 담는 변수의 타입은 그 클로저와 완전히 동일하다.
- 고로 딱 그 만큼의 메모리만 사용한다.
- std::function으로 선언된 클로저를 담는 변수의 타입은 std::function 템플릿의 한 인스턴스이다.
- 메모리 크기는 담을 함수의 형식(서명)에 대해 고정되어 있다.
- 헌데 그 크기가 클로저를 저장하기에 부족할 수도 있다.
- 그럴 땐 힙 메모리를 할당해서 저장하게 된다.
- auto로 선언된 클로저를 담는 변수의 타입은 그 클로저와 완전히 동일하다.
- 거의 항상 std::function이 auto보다 느리다.
- 인라인화(inlining)를 제한하고 간접 함수 호출을 산출하는 구현 세부사항 때문이다.
auto의 장점
- 변수 초기화 누락을 방지한다.
- 장황한 변수 선언을 피한다.
- 클로저를 직접 담을 수 있다.
- 타입 단축(type shortcut) 문제를 피할 수 있다.
- 리팩토링(refectoring)이 수월하다.
타입 단축(type shortcut) 문제
std::vector<int> v;
...
unsigned sz = v.size();
// v.size()의 타입은 std::vector<int>::size_type이다.
// 헌데, 그냥 unsigned에 넣어 버린다.
- 32비트 Windows에서는 unsigned와 std::vector<int>::size_type은 같은 크기이다.
- 64비트 Windows에서는 unsigned는 32비트지만, std::vector<int>::size_type은 64비트이다.
- 즉, 문제가 발생할 수 있는 코드이다.
- 하지만 auto를 사용하면 그냥 해결이다.
std::vector<int> v;
...
auto sz = v.size(); // 타입은 std::vector<int>::size_type
auto를 선호하면 좋은 추가 예시
std::unordered_map<std::string, int> m;
...
for(const std::pair<std::string, int>& p : m)
{
...
}
- std::unordered_map의 키는 const이나, 위 코드는 그렇지 않다.
- 이 경우, 컴파일러는 std::pair<const std::string, int>들을 어떻게든 std::pair<std::string, int>에 우겨넣으려 한다.
- 즉, 임시 객체를 생성하고,
- m의 각 객체를 복사하고,
- 참조 p를 그 임시 객체에 묶는다.
std::unordered_map<std::string, int> m;
...
for(const auto& p : m)
{
...
}
- auto를 사용하면 그냥 해결된다.
auto의 문제점
- auto 변수의 타입 추론이 예상하는 바와 전혀 다를 경우가 있다.(항목 2, 항목 6 참조)
- 소스 코드의 가독성(readablility)에 문제가 생긴다.
- auto는 필수가 아니라 선택이다.
- IDE 기능으로 완화되는 경우가 많다.
- 객체 타입을 추상적으로만 파악해도 괜찮은 상황들에선 괜찮다.
- 이 경우 이름만 잘 지어주면 된다.
728x90
'개발 > Effective Modern C++' 카테고리의 다른 글
[Effective Modern C++] 4. 추론 타입 파악하기 (0) | 2025.02.10 |
---|---|
[Effective Modern C++] 3. decltype (0) | 2025.02.07 |
[Effective Modern C++] 2. auto 타입 추론 규칙 (0) | 2025.02.06 |
[Effective Modern C++] 1. 템플릿 타입 추론 규칙 (0) | 2025.02.05 |
Comments