완벽한 전달
Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 104개 중 84번째.
argument를 수용하고 이를 다른 function에 pass하는 템플릿 함수를 작성할 때 문제가 발생합니다. 원래 argument가 lvalue인지 rvalue인지 어떻게 보존할 수 있을까요? Perfect forwarding은 argument를 전달받은 그대로 전달하여 값 범주를 유지함으로써 이 문제를 해결합니다.
핵심 요소는 forwarding reference(템플릿 문맥에서는 T&&로 작성됨)와 std::forward입니다. forwarding reference는 lvalue와 rvalue 모두에 바인딩할 수 있으며, std::forward는 argument를 원래 type으로 조건부 캐스팅합니다:
#include <iostream>
#include <utility>
void process(int& x) { std::cout << "lvalue: " << x << "\n"; }
void process(int&& x) { std::cout << "rvalue: " << x << "\n"; }
template<typename T>
void wrapper(T&& arg) {
process(std::forward<T>(arg));
}
int main() {
int n = 10;
wrapper(n); // process(int&) 호출 - lvalue 보존됨
wrapper(20); // process(int&&) 호출 - rvalue 보존됨
}std::forward가 없으면 이름이 지정된 parameter는 rvalue가 전달된 경우에도 function 내부에서는 항상 lvalues입니다. 그러면 constructors 또는 다른 functions에 forwarding할 때 move semantics가 올바르게 작동하지 않습니다.
완벽한 전달은 팩토리 함수, 래퍼 클래스 및 이동과 복사에 관한 호출자의 의도를 보존하면서 객체를 생성하거나 함수를 호출해야 하는 모든 제네릭 코드를 작성하는 데 필수적입니다.
챌린지
쉬움인수에 전달된 값 범주를 보존하면서 객체를 생성하여 완벽한 전달을 보여 주는 팩토리 함수를 만들어 보겠습니다. 인수를 클래스 생성자에 전달하는 일반적인 make_object function을 만들고, lvalue는 복사되고 rvalue는 이동되도록 합니다.
코드를 세 개의 파일로 구성합니다.
Widget.h: 데이터를 어떻게 받는지 추적하는Widgetclass를 정의합니다.Widget은std::stringname과intvalue를 저장해야 합니다. 두 개의 생성자를 제공하세요.const std::string&(lvalue reference)와int를 받는 생성자:Widget constructed (copy): [name]을 출력합니다.std::string&&(rvalue reference)와int를 받는 생성자:Widget constructed (move): [name]을 출력하고 문자열을 이동합니다.
또한
[name]: [value]를 출력하는display()method를 추가하세요.Factory.h: 완벽한 전달 팩토리function을 만듭니다.name에 대한 전달 참조와 value에 대한 일반적인
int를 받는 템플릿functionmake_object를 작성하세요. 원래 값 범주를 보존하면서 name 인수를Widget생성자에 전달하려면std::forward를 사용하세요.이
function은 생성된Widget을 값으로 반환해야 합니다.main.cpp: 두 개의 입력을 읽습니다.- name(문자열)
- value(정수)
두 개의 widget을 생성하여 완벽한 전달을 보여 주세요.
- 먼저 name을 local 변수에 저장하고 lvalue로
make_object에 전달합니다. - 그다음 임시 문자열(name에
"_temp"를 연결한 문자열)을 rvalue로make_object에 직접 전달합니다.
각 widget을 생성한 후 해당
display()method를 호출하세요.
예를 들어 입력이 Gadget과 42인 경우:
Widget constructed (copy): Gadget
Gadget: 42
Widget constructed (move): Gadget_temp
Gadget_temp: 42입력이 Device와 100인 경우:
Widget constructed (copy): Device
Device: 100
Widget constructed (move): Device_temp
Device_temp: 100핵심은 make_object function이 lvalue를 받았는지 rvalue를 받았는지 알지 못하지만, std::forward를 사용하면 Widget 생성자를 호출할 때 해당 정보를 보존한다는 점입니다. 이를 통해 생성자 오버로드 해결이 적절한 버전을 선택할 수 있으므로, 임시 객체는 효율적으로 이동하고 이름이 있는 변수는 안전하게 복사할 수 있습니다.
직접 해보기
#include <iostream>
#include <string>
#include "Widget.h"
#include "Factory.h"
using namespace std;
int main() {
// 입력 읽기
string name;
int value;
cin >> name >> value;
// TODO: name을 lvalue로 전달하여 첫 번째 widget 생성
// name을 지역 변수에 저장하고 make_object에 전달
// TODO: 첫 번째 widget에서 display() 호출
// TODO: 임시 문자열을 rvalue로 전달하여 두 번째 widget 생성
// (name + "_temp")를 make_object에 직접 전달
// TODO: 두 번째 widget에서 display() 호출
return 0;
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
직접 연습해 보세요: 온라인 C++ 컴파일러