Menu
Coddy logo textTech

Идеальная передача (Perfect Forwarding)

Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 84 из 104.

При написании шаблонных функций, которые принимают аргументы и передают их другим функциям, возникает проблема: как сохранить информацию о том, был ли исходный аргумент lvalue или rvalue? Идеальная передача (Perfect forwarding) решает эту проблему, передавая аргументы именно в том виде, в котором они были получены, сохраняя их категорию значения.

Ключевыми компонентами являются перенаправляющие ссылки (записываемые как 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 именованные параметры всегда являются lvalue внутри функции, даже если было передано rvalue. Это предотвратило бы правильную работу семантики перемещения при передаче в конструкторы или другие функции.

Идеальная передача (perfect forwarding) необходима для написания фабричных функций, классов-оберток и любого обобщенного кода, которому требуется создавать объекты или вызывать функции, сохраняя при этом намерение вызывающей стороны относительно перемещения или копирования.

challenge icon

Задание

Легко

Давайте создадим функцию-фабрику, которая демонстрирует идеальную передачу (perfect forwarding), создавая объекты и сохраняя категорию значения (value category) переданных ей аргументов. Вы создадите универсальную функцию make_object, которая перенаправляет аргументы конструктору класса, гарантируя, что lvalue копируются, а rvalue перемещаются.

Вы организуете свой код в трех файлах:

  • Widget.h: Определите класс Widget, который отслеживает, как он получает свои данные.

    Ваш Widget должен хранить имя std::string и значение int. Предусмотрите два конструктора:

    • Один, который принимает const std::string& (lvalue-ссылка) и int — выводит Widget constructed (copy): [name]
    • Один, который принимает std::string&& (rvalue-ссылка) и int — выводит Widget constructed (move): [name] и перемещает строку

    Также добавьте метод display(), который выводит [name]: [value].

  • Factory.h: Создайте свою функцию-фабрику с идеальной передачей.

    Напишите шаблонную функцию make_object, которая принимает универсальную ссылку (forwarding reference) для имени и обычный int для значения. Используйте std::forward для передачи аргумента имени в конструктор Widget, сохраняя его исходную категорию значения.

    Функция должна возвращать созданный Widget по значению.

  • main.cpp: Считайте два входных значения:
    1. Имя (строка)
    2. Значение (целое число)

    Продемонстрируйте идеальную передачу, создав два виджета:

    1. Сначала сохраните имя в локальную переменную и передайте ее в make_object как lvalue
    2. Затем передайте временную строку (то же имя, объединенное с "_temp") напрямую в make_object как rvalue

    После создания каждого виджета вызовите его метод display().

Например, при входных данных 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 не знает, получила ли она 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: Создать первый виджет, передав имя как lvalue
    // Сохранить имя в локальную переменную и передать её в make_object
    
    
    // TODO: Вызвать display() для первого виджета
    
    
    // TODO: Создать второй виджет, передав временную строку как rvalue
    // Передать (name + "_temp") напрямую в make_object
    
    
    // TODO: Вызвать display() для второго виджета
    
    
    return 0;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование