Menu
Coddy logo textTech

완벽한 전달

Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 104개 중 84번째.

argument를 수용하고 이를 다른 functionpass하는 템플릿 함수를 작성할 때 문제가 발생합니다. 원래 argumentlvalue인지 rvalue인지 어떻게 보존할 수 있을까요? Perfect forwardingargument를 전달받은 그대로 전달하여 값 범주를 유지함으로써 이 문제를 해결합니다.

핵심 요소는 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가 올바르게 작동하지 않습니다.

완벽한 전달은 팩토리 함수, 래퍼 클래스 및 이동과 복사에 관한 호출자의 의도를 보존하면서 객체를 생성하거나 함수를 호출해야 하는 모든 제네릭 코드를 작성하는 데 필수적입니다.

challenge icon

챌린지

쉬움

인수에 전달된 값 범주를 보존하면서 객체를 생성하여 완벽한 전달을 보여 주는 팩토리 함수를 만들어 보겠습니다. 인수를 클래스 생성자에 전달하는 일반적인 make_object function을 만들고, lvalue는 복사되고 rvalue는 이동되도록 합니다.

코드를 세 개의 파일로 구성합니다.

  • Widget.h: 데이터를 어떻게 받는지 추적하는 Widget class를 정의합니다.

    Widgetstd::string name과 int value를 저장해야 합니다. 두 개의 생성자를 제공하세요.

    • 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를 받는 템플릿 function make_object를 작성하세요. 원래 값 범주를 보존하면서 name 인수를 Widget 생성자에 전달하려면 std::forward를 사용하세요.

    function은 생성된 Widget을 값으로 반환해야 합니다.

  • main.cpp: 두 개의 입력을 읽습니다.
    1. name(문자열)
    2. value(정수)

    두 개의 widget을 생성하여 완벽한 전달을 보여 주세요.

    1. 먼저 name을 local 변수에 저장하고 lvalue로 make_object에 전달합니다.
    2. 그다음 임시 문자열(name에 "_temp"를 연결한 문자열)을 rvalue로 make_object에 직접 전달합니다.

    각 widget을 생성한 후 해당 display() method를 호출하세요.

예를 들어 입력이 Gadget42인 경우:

Widget constructed (copy): Gadget
Gadget: 42
Widget constructed (move): Gadget_temp
Gadget_temp: 42

입력이 Device100인 경우:

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;
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨

직접 연습해 보세요: 온라인 C++ 컴파일러