Перейти к содержанию

Идеальная передача (perfect forwarding)

Начало урока

Идеальная передача (perfect forwarding)

Передача аргументов через обёртку без потери их категории значения

Главная идея: Идеальная передача - это приём, позволяющий обобщённому шаблону функции принять любой аргумент и передать его дальше другой функции ровно таким, каким он был получен, сохранив, был ли это lvalue или rvalue, и был ли он const или нет. Работу обеспечивают три составляющие: forwarding-ссылка (T&& при выводимом типе), правила сворачивания ссылок, применяемые компилятором, и std::forward - условное приведение, восстанавливающее исходную категорию значения в точке вызова.
К концу вы сможете:
  • объяснить разницу между rvalue-ссылкой и forwarding-ссылкой, хотя обе записываются как T&&,
  • вручную применять четыре правила сворачивания ссылок,
  • правильно использовать std::forward<T> (и объяснить, почему std::move здесь ошибочен),
  • писать вариативные фабричные функции и пересылающие конструкторы, избегая ловушки жадного конструктора.

1. Задача: обёртка, сохраняющая категорию значения

Категории значений вы уже знаете из курса о семантике перемещения: lvalue именует долгоживущий объект (у него можно взять адрес), а rvalue - это временный объект, из которого можно перемещать. Семантика перемещения выгодна лишь тогда, когда временный объект действительно распознан как rvalue и его ресурсы крадутся, а не копируются.

Представьте тонкую обёртку - слой логирования, фабрику, emplace контейнера - которая получает аргумент и сразу отдаёт его целевой функции sink. Мы хотим, чтобы обёртка была невидимой: если вызывающий передал временный объект, sink должен увидеть временный (и переместить); если передал именованный объект, sink должен увидеть lvalue (и скопировать). Наивные попытки проваливаются:

  • void wrap(T v) - приём по значению, навязывающий лишнюю копию или перемещение каждого аргумента.
  • void wrap(const T& v) - всегда lvalue-ссылка на const; sink никогда не сможет переместить, даже из настоящего временного.
  • void wrap(T& v) - полностью отвергает rvalue и const-объекты.

Писать отдельную перегрузку под каждую комбинацию const/не-const и lvalue/rvalue не масштабируется: функции с n параметрами понадобилось бы 2^n перегрузок. Идеальная передача решает всю задачу одним шаблоном.

Точная формулировка цели: аргумент, пришедший как lvalue, уходит как lvalue; аргумент, пришедший как rvalue, уходит как rvalue - и константность сохраняется в обоих случаях.

Продолжите этот урок на YourAIZone

В полном уроке — объяснение целиком, разобранные примеры, частые ошибки, тесты с мгновенной проверкой и ИИ-репетитор. Начните сразу как гость или создайте аккаунт, чтобы сохранять прогресс.

В полном уроке

  • Объяснение целиком
  • Разобранные примеры
  • Частые ошибки и как их избежать
  • Тестовые вопросы: 15
  • ИИ-репетитор, который отвечает на вопросы по теме
  • Отслеживание прогресса для ученика, родителей и учителя