티스토리 뷰
우리가 STL을 사용하게 되면 많은 곳에서 템플릿 형식을 접하게 된다. 그러나 템플릿 형식의 타입이 결정되는지는 잘 알지 못한다. 항목 1에서 타입이 어떻게 연역 되는지 알아보도록 한다. 이 책에서는 템플릿 형식이 연역되는 규칙을 3가지로 분류하였다.
연역 규칙
- ParamType이 포인터 또는 참조 형식이지만 보편 참조는 아님
- ParamType이 보편 참조임
- ParamType이 포인터도 아니고 참조도 아님
규칙들을 정리하기 전에 보편 참조라는 생소한 용어가 보일 것이다. 여기서 말하는 보편 참조란 && 를 말한다. 우측값 참조와 쓰는 형태가 같지만 auto 나 template 에서는 보편 참조라고 부른다. 그 이유는 보편 참조는 우측값 참조이거나 혹은 왼값 참조일수도 있기 때문이다. 우측값이 궁금하다면 아래의 포스트를 읽어보자.
1번 규칙을 다시 보면 보편 참조는 아니라고 하였으니, * 나 & 로 연역되는 경우를 말한다. 3번 규칙의 경우는 값 복사가 일어나는 경우를 의미한다.
공통 규칙
책과는 다르게 1번과 2번 규칙에서 공통적인 부분에 대해서 정리하려고 한다. 템플릿 연역 형식의 대부분은 경험이나 직관으로 이해가 가능하다. const 를 사용한 아래의 예제를 보자.
template<typename T>
void f1(T& param);
template<typename T>
void f2(const T& param);
int x = 27;
const int cx = x;
f1, f2 두가지 함수에 x 와 cx 를 인자로 사용했을때 const 는 어떻게 될까? T 의 타입과 param 의 타입의 결과를 직관적으로 해석해 보자.
결과는 다음과 같다.
f1(x); // T: int, ParamType: int&
f1(cx); // T: const int, ParamType: const int&
f2(x); // T: int, ParamType: const int&
f2(cx); // T: int, ParamType: cosnt int&
주목해야 하는 결과는 f2(cx) 이다. cx 가 const int 타입임에도 T 는 int 타입이 된다. 이것만 주의하면 나머지는 직관적인 해석이 가능하다.
위의 예제에서는 1번 규칙을 사용하였지만 포인터나 2번 규칙에서도 똑같이 동작한다.
다시 정리하면 상수성은 보존되기 때문에 값의 변화를 원치 않는 경우에 컴파일러는 에러를 뿜을 것이라는 것이다. 만약 모르는 함수를 사용하게 되는 경우 원치 않는 결과를 피할 수 있다. 이는 3가지 규칙에 모두 적용되어야 하는 점이다.
1. ParamType이 포인터 또는 참조 형식이지만 보편 참조는 아님
이 규칙의 경우에는 const 와 똑같이 적용된다. 차이가 있다면 const 가 아니라 * 이나 & 라는 점이다.
template<typename T>
void f1(T& param);
template<typename T>
void f2(T* param);
int x = 27;
int& rx = x;
int* px = &x;
f1(x); // T: int, ParamType: int&
f1(rx); // T: int, ParamType: int&
f2(&x); // T: int, ParamType: int*
f2(px); // T: int, ParamType: int*
2. ParamType이 보편 참조임
보편 참조는 move 함수와 forward 함수의 차이를 이해하기 위해서는 필수임으로 어떻게 동작하는지 잘 알아두어야 한다. 다행히도 동작원리는 어렵지 않다. 인자가 왼값이면 왼값 참조를 하고 인자가 우측값이면 우측값 참조를 한다.
template<typename T>
void f(T&& param);
int x = 27;
// 왼값
f(x); // T: int, ParamType: int&
// 우측값
f(27); // T: int, ParamType: int&&
3. ParamType이 포인터도 아니고 참조도 아님
3번의 제목은 어렵게 써놨는데 사실 pass-by-value인 상황이다. 동작하는 방법도 template이 아닐때와 매우 비슷하다. 값 복사 전달이기 때문에 대부분의 형식이 무시된다.
- 인자의 타입이 참조 형식인 경우에도 참조는 무시된다.
- 인자가 const인 경우에도 const는 무시한다.
- volatile인 경우에도 무시한다.
배열 인수
타입이 배열인 경우에는 약간의 문제가 발생한다. 그 이유는 배열이 포인터로 붕괴하기 때문이다. 이와 같은 문제를 피하기 위해서는 참조 형식으로 매개변수를 받으면 해결된다. 참조 형식으로 받는 경우 포인터로 붕괴하지 않고 T 가 배열 타입으로 지정된다. 더 중요한 것은 배열에 담긴 원소들의 갯수를 알아낼 수 있다는 점이다.
template<typename T, std::size_t N>
void f(T (¶m)[N]);
int a[3] = {1, 2, 3};
f(a);
위의 예제에서 N 이 원소의 갯수이다.
함수 인수
함수 형식의 인수도 배열과 마찬가지로 포인터로 붕괴한다.
void someFunc(int, double);
template<typename T>
void f1(T param);
template<typename T>
void f2(T& param);
f1(someFunc); // void (*)(int, double)
f2(someFunc); // void (&)(int, double)
참고 서적
스콧 마이어스, Effective Modern C++
'C++ > Effective Modern C++' 카테고리의 다른 글
[Effective Modern C++] 항목 6. auto가 원치 않은 형식으로 연역될 때에는 명시적 형식의 초기치를 사용하라 (0) | 2019.09.08 |
---|---|
[Effective Modern C++] 항목 5. 명시적 형식 선언보다는 auto를 선호하라 (0) | 2019.09.08 |
[Effective Modern C++] 항목 4. 연역된 형식을 파악하는 방법을 알아두라 (0) | 2019.09.02 |
[Effective Modern C++] 항목 3. decltype의 작동 방식을 숙지하라 (0) | 2019.09.02 |
[Effective Modern C++] 항목 2. auto의 형식 연역 규칙을 숙지하라 (0) | 2019.08.25 |
- Total
- Today
- Yesterday
- 람다
- 포인터
- std::move
- Overloading
- C
- C++
- 보편 참조
- MOVE
- const
- forward
- CPP
- detach
- 다이소
- std::forward
- C++11
- Join
- Modern
- 보편참조
- thread
- C++14
- Future
- async
- Perfect
- 발아시기
- Forwarding
- Override
- Unreal
- Effective Modern C++
- auto
- Effective
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
29 | 30 | 31 |