기존에는 보편 참조(universal reference)라고 불렸는데
c++ 17을 통해 전달 참조(forwarding reference)로 불리게 되었다
전달 참조를 배우기 전,
오른 값 참조를 다시 복습하면 아래처럼 동작했다
class Knight
{
public:
Knight() {}// 기본 생성자
Knight(const Knight&) {}// 복사 생성자
Knight(Knight&&) noexcept {}// 이동 생성자
};
void Test_RValueRef(Knight&& k)// 오른값 참조
{
}
int main()
{
Knight k1;
//Test_RValueRef(k1);// 왼값은 받아주지 않음
Test_RValueRef(std::move(k1));// 캐스팅을 통해 왼값을 오른값으로
}
전달 참조는
형식 연역(type deduction)이 일어날 때 만 발생한다 ex. template, auto
왼 값이 들어오면 왼 값 참조로
오른 값이 들어오면 오른 값 참조로
알잘딱으로 오른 값 참조와 왼 값 참조를 처리해서 동작한다
아래를 통해 알아보자
class Knight
{
public:
Knight() {}// 기본 생성자
Knight(const Knight&) {}// 복사 생성자
Knight(Knight&&) noexcept {}// 이동 생성자
};
template<typename T>
void Test_ForwardingRef(T&& param)// 전달 참조
{
}
int main()
{
Knight k1;
Test_ForwardingRef(k1);// 왼값 참조로 자동 변환
Test_ForwardingRef(std::move(k1));// 오른값 참조로 자동 변환
auto&& k2 = k1;// Knight& 로 추론
auto&& k3 = std::move(k1);// Knight&& 로 추론
}
하지만, template을 써도 전달참조에 const를 붙이면
아래와 같이 왼값 참조는 받아주지 않으며 전달 참조로 사용되지 않는데
class Knight
{
public:
Knight() {}// 기본 생성자
Knight(const Knight&) {}// 복사 생성자
Knight(Knight&&) noexcept {}// 이동 생성자
};
void Test_RValueRef(Knight&& k)// 오른값 참조
{
}
template<typename T>
void Test_ForwardingRef(const T&& param)// 전달 참조에 const를 붙이면 오른값 참조만 가능
{
}
int main()
{
Knight k1;
//Test_ForwardingRef(k1);// 왼값은 받아주지 않음
Test_ForwardingRef(std::move(k1));// 오른값 참조로 받아줌
}
전달 참조를 통해,
왼 값과 오른 값을 동시에 처리해 주니 편하지만
함수 내부에서는 왼 값이냐 오른 값이냐에 따라 처리가 달라져야 하기 때문에
이를 어떻게 해결할지가 문제이다
이에 대한 해결법은 잠시후에 알아보고 또 다른 문제에 대해 알아보자
오른 값 참조 타입인데 왼 값인 건에 대하여
아래를 통해 알아보자
class Knight
{
public:
Knight() {}// 기본 생성자
Knight(const Knight&) {}// 복사 생성자
Knight(Knight&&) noexcept {}// 이동 생성자
};
void Test_RValueRef(Knight&& k)// 오른값 참조
{
}
template<typename T>
void Test_ForwardingRef(T&& param)// 전달 참조
{
}
int main()
{
Knight k1;
Knight& k4 = k1;// 왼값 참조
Knight&& k5 = std::move(k1);// 오른값 참조
//Test_RValueRef(k5);// k5의 타입은 오른값 참조 타입이지만 사실상 왼값이고 따라서 이는 작동이 불가능하다
Test_RValueRef(std::move(k5));// 따라서 이와 같이 오른값으로 캐스팅 해줘야한다
}
이처럼 std::move를 했다고 해서 k5가 오른값이 되지 않는다
아래의 예시를 통해 전달참조 내부에서는 어떻게 작동하는지 살펴보자
class Knight
{
public:
Knight() {}// 기본 생성자
Knight(const Knight&) {}// 복사 생성자
Knight(Knight&&) noexcept {}// 이동 생성자
};
void Test_RValueRef(Knight&& k)// 오른값 참조
{
}
void Test_Copy(Knight k)
{
}
template<typename T>
void Test_ForwardingRef(T&& param)// 전달 참조
{
// param 자체는 왼값이기 때문에
// param을 std::move를 또 해줘야지 오른값 참조로 인식하고 이동 생성자가 실행이된다
//Test_Copy(param);// param은 왼값이니 복사 생성자 호출
Test_Copy(std::move(param));// param을 오른값으로 캐스팅 해줬으니 이동 생성자 호출
}
int main()
{
Knight k1;
Test_ForwardingRef(std::move(k1));// 이렇게 오른값으로 캐스팅 해도
// 사실상 타고 들어가면 왼값이다
}
이제 다시 돌아와서,
함수 내부에서 왼 값이냐 오른 값이냐에 따라 처리가 달라져야 하는 부분을
해결해 주는 std::forward를 알아보자
class Knight
{
public:
Knight() {}// 기본 생성자
Knight(const Knight&) {}// 복사 생성자
Knight(Knight&&) noexcept {}// 이동 생성자
};
void Test_RValueRef(Knight&& k)// 오른값 참조
{
}
void Test_Copy(Knight k)
{
}
template<typename T>
void Test_ForwardingRef(T&& param)// 전달 참조
{
// 왼값 참조라면 복사
//Test_Copy(param);
// 오른값 참조라면 이동
//Test_Copy(std::move(param));
Test_Copy(std::forward<T>(param));// std::forward를 사용하면 위를 해결 가능
}
int main()
{
Knight k1;
Test_ForwardingRef(k1);// 복사 생성자 호출
Test_ForwardingRef(std::move(k1));// 이동 생성자 호출
}
'C++ > [루키스] Modern C++' 카테고리의 다른 글
[MC++] 스마트 포인터 (Smart pointer) (0) | 2023.07.03 |
---|---|
[MC++] Lambda (0) | 2023.07.02 |
[MC++] 오른 값 참조 (Rvalue reference)와 std::move (0) | 2023.07.02 |
[MC++] override, final (0) | 2023.07.01 |
[MC++] delete (0) | 2023.07.01 |