Menu
Coddy logo textTech

완벽한 전달

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

인자를 받아 다른 함수로 전달하는 템플릿 함수를 작성할 때 한 가지 문제가 발생합니다. 원래 인자가 lvalue였는지 rvalue였는지 어떻게 유지할 수 있을까요? 완벽한 전달(Perfect forwarding)은 인자를 받은 그대로 전달하여 값 범주(value category)를 유지함으로써 이 문제를 해결합니다.

핵심 요소는 전달 참조(forwarding references)(템플릿 컨텍스트에서 T&&로 작성됨)와 std::forward입니다. 전달 참조는 lvalue와 rvalue 모두에 바인딩될 수 있으며, std::forward는 인수를 원래의 타입으로 조건부 캐스팅합니다:

#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가 없으면, rvalue가 전달되었더라도 이름이 있는 매개변수는 함수 내부에서 항상 lvalue가 됩니다. 이는 생성자나 다른 함수로 전달할 때 이동 의미론(move semantics)이 올바르게 작동하는 것을 방해합니다.

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

challenge icon

챌린지

쉬움

전달된 인수의 값 카테고리(value category)를 보존하면서 객체를 생성하는 완벽한 전달(perfect forwarding)을 보여주는 팩토리 함수를 만들어 보겠습니다. 인수를 클래스 생성자로 전달하여 lvalue는 복사되고 rvalue는 이동되도록 보장하는 제네릭 make_object 함수를 작성하게 됩니다.

코드는 다음 세 개의 파일로 구성됩니다:

  • Widget.h: 데이터를 어떻게 전달받는지 추적하는 Widget 클래스를 정의합니다.

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

    • const std::string& (lvalue 참조)와 int를 받는 생성자 — Widget constructed (copy): [name]를 출력합니다.
    • std::string&& (rvalue 참조)와 int를 받는 생성자 — Widget constructed (move): [name]를 출력하고 문자열을 이동(move)시킵니다.

    또한 [name]: [value]를 출력하는 display() 메서드를 추가하세요.

  • Factory.h: 완벽한 전달 팩토리 함수를 작성합니다.

    name에 대해서는 전달 참조(forwarding reference)를 받고, value에 대해서는 일반 int를 받는 템플릿 함수 make_object를 작성하세요. std::forward를 사용하여 name 인수의 원래 값 카테고리를 유지하면서 Widget 생성자에 전달합니다.

    이 함수는 생성된 Widget을 값으로 반환해야 합니다.

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

    두 개의 위젯을 생성하여 완벽한 전달을 시연합니다:

    1. 첫째, 이름을 지역 변수에 저장하고 이를 lvalue로서 make_object에 전달합니다.
    2. 둘째, 임시 문자열(동일한 이름 뒤에 "_temp"를 연결한 것)을 rvalue로서 make_object에 직접 전달합니다.

    각 위젯을 생성한 후, 해당 위젯의 display() 메서드를 호출하세요.

예를 들어, 입력값이 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 함수가 lvalue를 받았는지 rvalue를 받았는지 알지 못하더라도, std::forward를 사용함으로써 Widget 생성자를 호출할 때 해당 정보를 보존한다는 점입니다. 이를 통해 생성자 오버로딩 해결(overload resolution)이 적절한 버전을 선택하게 되어, 이름이 있는 변수는 안전하게 복사하고 임시 객체는 효율적으로 이동할 수 있게 됩니다.

치트 시트

완벽한 전달(Perfect forwarding)은 템플릿 함수가 인수를 다른 함수로 전달할 때 원래의 값 범주(lvalue 또는 rvalue)를 유지할 수 있게 해줍니다.

이 메커니즘은 std::forward와 결합된 전달 참조(템플릿 컨텍스트에서의 T&&)를 사용합니다:

#include <utility>

template<typename T>
void wrapper(T&& arg) {
    process(std::forward<T>(arg));
}

전달 참조는 lvalue와 rvalue 모두에 바인딩될 수 있습니다. std::forward<T>는 인수를 원래의 타입으로 조건부 캐스팅합니다.

std::forward가 없으면, rvalue가 전달되었더라도 함수 내부의 이름 있는 매개변수는 항상 lvalue로 취급됩니다. 이는 이동 의미론(move semantics)이 올바르게 작동하는 것을 방해합니다.

값 범주 보존을 보여주는 예제:

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 n = 10;
wrapper(n);       // Calls process(int&) - lvalue preserved
wrapper(20);      // Calls process(int&&) - rvalue preserved

완벽한 전달은 팩토리 함수, 래퍼 클래스, 그리고 이동 대 복사에 관한 호출자의 의도를 유지하면서 객체를 생성하거나 함수를 호출하는 제네릭 코드에 필수적입니다.

직접 해보기

#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실력 점검

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

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