프로그래밍 언어 공부/C++

[Modern C++] 전달 참조

설탕중독 2022. 7. 18. 15:27

 

[Modern C++] 전달 참조

 

오늘은 전달 참조에 대해 알아보자.

C++17 이전에는 전달 참조가 보편 참조로 불렸다.

보편 참조 == 전달 참조, 같은 의미이니 잘 숙지해두자.

 

 

일단 전달 참조 같은 경우 오른값 참조 문법과 거의 같다고 보면 된다.

1-1. 오른값 참조 복습겸 실습

Hoon 클래스를 생성하고 기본, 복사, 이동 생성자를 각각 만들어주었다.

그리고 별도의 함수를 생성해주고 오른값 참조를 받도록 선언하였다.

메인 함수에서 클래스 객체를 생성하고 별도로 만들었던 함수를 호출하며 전달해주는 값으로 클래스 객체를

넣어주는데 일반적으로는 넣을 수 없으니 std::move를 통해 오른값으로 캐스팅 후 넣어주었다.

 

하지만 오늘의 주제는 전달 참조이며 전달 참조는 오른값 참조와 눈으로 보기엔 다름이 없다.

즉 '&&' 처럼 코드가 만들어져 있어도 무조건 오른값 참조는 아니라는 소리이다. 

 

 

1-2. 전달 참조, 오른쪽 참조

28~32번 코드를 보면 템플릿을 통해 새로운 함수를 만들어주었고 전달받는 값으로는 '&&'로 선언해주었다.

그리고 메인 함수로 넘어가서 해당 함수를 호출해주었다.

39번 코드를 보면 37번 코드처럼 std::move를 통해 오른값 참조로 변환하여 넣어주어서 정상 작동한다.

근데 40번 코드를 보면 왼값 참조를 그냥 넣어주었는데 에러가 발생하지 않고 심지어 일반 참조 타입으로 

정상 호출되는 것을 알 수 있다.

 

해당 문제는 템플릿에만 해당되는 것이 아니라 auto에도 적용된다. 

정리하자면 컴파일러가 자동으로 타입을 추측해서 변환해주는 '형식 연역'이 일어날 때 전달 참조가 발생한다.

즉 29번 코드와 같이 템플릿 혹은 auto를 사용하는 것을 전달 참조라고 한다.

 

하지만 해당 코드를 변경하게 되면 전달 참조로서 동작하지 않는다. 29번 코드와 같이 'T&&'로만 만들어져

있어야 전달 참조이고 앞에 const 같은 것이 붙게 되면 오른값 참조로서 동작한다.

 

그렇다면 전달 참조는 왜 존재하는 것일까?

그냥 오른값 참조로서만 사용하면 안 되는 것일까?

 

전달 참조가 만들어진 이유는 효율성 때문이다.

왼값 참조와 오른값 참조를 동시에 가지고 있기 때문에 각각 만들어주지 않고 템플릿과 auto를

활용해 효율적으로 만들 수 있기 때문에 전달 참조가 사용된다.

(그렇다고 자주 사용되는 것은 아니다.)

 

 

다음은 자주 헷갈리는 부분을 알아보자

2-1. 헷갈리는 부분

참조 타입의 h2를 선언하고 미리 선언했었던 Hoon 클래스 타입의 h1을 받아주었다.

그리고 오른값 참조 타입으로 선언된 h3에 std::move를 이용하여 h2를 넣어주었다.

(참고로 Test1이라는 함수는 오른값을 받아주는 형태의 함수이다.)

오른값 참조 타입의 h3를 Test1 함수에 넣어주려고 하는데 이상하게 에러가 발생한다.

오른값을 받아주는 함수에 오른값 참조 타입의 변수를 넣었는데 왜 에러가 발생하는 것일까

 

그 이유는 h3가 오른값 참조 타입이지만 h3 자체는 왼값이기 때문이다.

오른값이라는 정의 자체가 단일식에서 벗어나면 사용할 수 없다고 했다. 즉 해당 라인을 벗어나면

다른 곳에서는 사용할 수가 없다. 하지만 h3의 경우 해당 라인을 벗어나서도 사용이 가능하다.

즉 타입만 오른값 참조 타입일 뿐 h3 자체는 왼값이다. 그래서 한 번 더 std::move를 이용해서 

함수에 넣어주어야 정상 작동한다.

 

 

진짜 문제는 지금부터이다.

위의 헷갈리는 문제에서 파생되는 문제인데 바로 전달 참조로 받아주었을 때 조건에 따라 

받아준 값을 왼값으로 처리해서 복사를 할지 아니면 오른값으로 처리해서 이동을 할지 선택해야 한다.

전달 참조 같은 경우 왼값과 오른값 모두 사용할 수 있기 때문에 이러한 문제가 생길 수 있다.

 

왼값인지 오른값인지 조건에 따라 나눠주는 기능을 따로 지원하는데 

std::forward가 해당 기능을 지원한다.

2-2. std::forward()

std::forward(value) 이렇게 사용하면 되고 value의 값이 왼값이라면 복사를 진행하고

value의 값이 오른값이라면 value는 오른값 참조를 가지고 있지만 value 자체가 왼값이기 때문에

한 번 더 std::move를 이용해서 오른값 참조로서 사용하게 된다.

 

즉 39번 코드가 실행되면 이동 생성자가 실행되고,

40번 코드가 실행되면 복사 생성자가 실행된다.