티스토리 뷰
[Effective Modern C++] 항목 30. 완벽 전달이 실패하는 경우들을 잘 알아두라.
pppgod 2019. 12. 15. 00:32완벽 전달(perfect forwarding)은 한 함수가 자신의 인수들을 다른 함수에 완벽히 전달하는 것을 의미한다. 전달할 때 객체 뿐만 아니라 왼값 또는 오른값 여부와 const 와 volatile 과 같은 형식까지도 보존하여 전달해야한다. 완벽 전달을 수행하는 함수는 다음과 같은 형태를 가진다.
template<typename T>
void fwd(T&& param)
{
f(std::forward<T>(param));
}
template<typename... Ts>
void fwd(Ts&&... params)
{
f(std::forward<T>(params)...);
}
위와 같은 형식을 가지는 대표적인 함수는 STL에서 emplace 함수들과 std::make_shared, std::make_unique 등이 위와 같은 형태를 사용한다. 이번 장에서 이야기할 완벽 전달이 실패하는 경우란 fwd 함수가 f 함수로 인자들을 완벽히 전달하지 못하는 상황을 의미한다. 몇가지 종류들을 알아보자.
중괄호 초기치
void f(const std::vector<int>& v);
f({ 1, 2, 3}); // compile 성공
fwd({ 1, 2, 3 }); // compile 실패
위 코드에서 f 로 바로 사용한 경우에는 std::initializer_list 으로부터 std::vector<int> 객체를 생성하여 에러가 발생하지 않는다. 하지만 fwd 의 경우에는 그렇지 못하고 컴파일 에러가 발생한다. 컴파일러는 fwd 로 전달된 인수들의 형식을 연역하고, 연역된 형식들을 f 의 매개변수 선언들과 비교한다. 비교를 수행할 때 아래 조건이 하나라도 만족하면 컴파일 에러가 발생한다.
- fwd 의 매개변수들 중 하나 이상에 대해 컴파일러가 형식을 연역하지 못하는 경우
- fwd 의 매개변수들 중 하나 이상에 대해 컴파일러가 형식을 잘못 연역하는 경우
위 문제에서는 std::initializer_list 가 될 수 없는 형태로 선언된 함수 템플릿 매개변수에 중괄호 초기치를 전달하여 문제가 발생하는 것이다. 이를 '비연역 문맥(non-deduced context)' 라고 부른다. 위 문제를 해결하는 항목2를 살펴보자.
널 포인터를 뜻하는 0 또는 NULL
문제는 간단하다. 0 또는 NULL은 정수로 인식하기 때문에 완벽하게 전달되지 못한다. nullptr 를 사용하면 간단하게 해결할 수 있다.
선언만 된 정수 static const 및 constexpr 자료 멤버
static const 자료 멤버와 static constexpr 자료 멤버는 클래스 안에서 정의할 필요 없이 선언만하면 된다. 그 이유는 컴파일러가 'const 전파(const propagation)' 를 적용해서, 메모리를 따로 마련할 필요가 없어지기 때문이다.
class Widget {
public:
static constexpr std::size_t MinVals = 28;
...
};
...
std::vector<int> widgetData;
widgetData.reserve(Widget::MinVals);
static 멤버 변수의 경우에는 사실 정의가 필수이다. 위 코드에서는 MinVals 를 사용하는 곳은 컴파일러가 28 을 배치하여 문제 없이 돌아가는 것이다. 그런데 만일 MinVals 의 포인터가 필요해지면 이 코드는 실패하게 된다. 완벽 전달 역시 문제가 발생한다.
f(Widget::MinVals); // OK
fwd(Widget::MinVals); // 링크 실패
첫번째 코드는 숫자 28 이 넘어가서 문제가 발생하지 않지만 fwd 를 거치게 되면 MinVals 의 주소를 사용하려고 하며 문제가 발생한다. 보편 참조에서 사용하는 참조 또한 포인터와 같은 것이기 때문에 포인터 접근을 하는것과 마찬가지이다. 이 문제를 해결하는 것은 아주 간단하다. 정의부를 구현해주면 된다.
constexpr std::size_t Widget::MinVals;
중복적재된 함수 이름과 템플릿 이름
f 함수가 함수를 인자로 받아서 사용한다고 가정해보자. 다음과 같을 것이다.
void f(int (*pf)(int));
void f(int pf(int));
위 두개 함수는 같은 의미를 가진다. 위 함수에 함수를 전달하면 다음과 같을 것이다.
int processVal(int value);
int processVal(int value, int priority);
f(processVal);
fwd(processVal); // error
f 에 전달하는 processVal 은 첫번째 processVal 가 가장 적절하므로 첫번째가 선택된다. 그런데 전달자 함수를 통해서 전달하려고 하면 오류가 발생한다. 그 이유는 fwd 함수에는 호출에 필요한 형식에 관한 정보가 전혀 없기 때문에 어떤 함수를 선택해야하는지 모른다.
중복적재를 사용하는 함수 뿐만 아니라 함수 템플릿을 사용하려고 해도 문제가 발생한다. processVal 자체에는 타입이 존재하지 않기 때문에 형식 연역도 이뤄질 수 없기 때문이다.
template<typename T>
T workOnVal(T param)
{ ... }
fwd(workOnVal); // 에러 발생
위 문제를 해결하는 방법은 타입을 지정해주는 것이다. 타입이 없어 연역을 하지 못하여 발생한 것이기 때문에 타입을 지정해주면 된다.
using ProcessFuncType =
int (*)(int);
ProcessFuncType processValPtr = processVal;
fwd(processValPtr);
fwd(static_cast<ProcessFuncType>(workOnVal));
비트필드
마지막으로 완벽 전달이 실패하는 경우는 비트필드를 사용할 때이다. 다음은 IPv4 헤더 파일이다.
struct IPv4Header {
std::uint32_t version:4,
IHL:4,
DSCP:6,
ECN:2,
totalLength:16;
...
};
void f(std::size_t sz);
IPv4Header h;
...
f(h.totalLength); // OK
fwd(h.totalLength); // error
std::size_t 를 사용하는 함수 fwd 에 totalLength 를 전달하고 하면 에러가 발생한다. 그 이유는 fwd 의 매개변수는 참조인데 반해 totalLength 는 비 const 비트필드이기 때문이다. C++ 표준에서는 "비 const 참조는 절대로 비트필드에 묶이지 않아야 한다" 는 조건이 있다. 그 이유는 비트필드들은 워드의 일부분으로 존재할 수 있는데, 임의의 비트들을 가르키는 포인터를 생성하는 방법이 없다. 다행히 우회하는 방법이 존재하는데, 그 방법은 복사본을 만들어 전달하는 것이다. 복사본에는 포인터를 생성할 수 있기 때문에 문제는 발생하지 않는다. 다음과 같이 구현할 수 있다.
auto length = static_cast<std::uint16_t>(h.totalLength);
fwd(length);
참고 서적
스콧 마이어스, Effective Modern C++
'C++ > Effective Modern C++' 카테고리의 다른 글
[Effective Modern C++] 항목 32. 객체를 클로저 안으로 이동하려면 초기화 갈무리를 사용하라 (0) | 2020.01.12 |
---|---|
[Effective Modern C++] 항목 31. 기본 갈무리 모드를 피하라 (0) | 2020.01.11 |
[Effective Modern C++] 항목 29. 이동 연산이 존재하지 않고, 저렴하지 않고, 적용되지 않는다고 가정하라 (0) | 2019.12.14 |
[Effective Modern C++] 항목 28. 참조 축약을 숙지하라 (0) | 2019.12.08 |
[Effective Modern C++] 항목 27. 보편 참조에 대한 중복적재 대신 사용할 수 있는 기법들을 알아 두라 (0) | 2019.12.08 |
- Total
- Today
- Yesterday
- std::move
- Forwarding
- forward
- 람다
- Future
- 포인터
- thread
- Perfect
- 발아시기
- async
- Override
- 보편 참조
- C++11
- auto
- Effective
- Effective Modern C++
- Unreal
- detach
- C++
- Join
- const
- 보편참조
- MOVE
- CPP
- Modern
- Overloading
- 다이소
- C
- std::forward
- C++14
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |