Прямая передача (C++)

(перенаправлено с «Передаваемая ссылка»)

Прямая передача (англ. Perfect Forwarding) — идиоматический механизм переноса атрибутов параметров в процедурах обобщённого кода языка C++. Он был стандартизирован в редакции стандарта C++11 с помощью функционала библиотеки STL и синтаксиса передаваемыx ссылок (англ. forwarding references), а также унифицирован для применения совместно с вариативными шаблонами[1][2].

Прямая передача используется в тех случаях, когда от функций и процедур обобщённого кода требуется оставлять неизменными фундаментальные свойства своих параметризованных аргументов, то есть[1]:

  • константный объект должен передаваться как объект, предназначенный только для чтения.
  • модифицируемый объект должен оставаться модифицируемым.
  • перемещаемый объект должен оставаться перемещаемым.

Практическое воплощение прямой передачи в стандарте языка реализовано с помощью функции std::forward из заголовочного файла <utility>[3][4]. Вследствие чего, комбинация специальных правил вывода для &&-ссылок и их свёртки позволяет создать функциональный шаблон, который принимает произвольные аргументы с фиксацией их типов и основных свойств (rvalue или lvalue). Сохранение этой информации предопределяет возможность передавать данные аргументы при вызове других функций и методов[5].

Предпосылки

Особое поведение параметров — временных ссылок

Рассмотрим простейший объект с двумя конструкторами — один копирует поле из std::string, второй перемещает.

class Obj {public:  Obj(const std::string& x) : field(x) {}  Obj(std::string&& x) : field(std::move(x)) {}  // std::move нужен!!private:  std::string field;}

Первая перегрузка конструктора — самая обычная из Си++03. А во второй std::move, и вот почему.

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

Вопросы начинаются, когда параметров много — приходится делать 4, 8, 16… конструкторов.

class Obj2 {public:  Obj2(const std::string& x1, const std::string& x2) : field1(x1), field2(x2) {}  Obj2(const std::string& x1, std::string&& x2) : field1(x1), field2(std::move(x2)) {}  // …и ещё две перегрузкиprivate:  std::string field1, field2;}

Существуют два способа не множить сущности, идиома «by-value+move» и метапрограммирование, и для последнего сделан второй механизм Си++11.

Склейка ссылок

Склейку (свёртку, коллапсирование) ссылок (англ. reference collapsing) лучше всего объяснит такой код.

using One = int&&;using Two = One&;    // тогда Two = int&

При переходе к передаваемым ссылкам выясняется не только тип переданного в функцию параметра, но также даётся оценка, является ли он rvalue или lvalue. Если переданный в функцию параметр является lvalue, то подставляемое значение тоже будет ссылкой на lvalue. При этом, отмечается, что объявление типа параметра шаблона в виде &&-ссылки может иметь интересные побочные эффекты. Например, проявляется необходимость явного указания инициализаторов для всех локальных переменных данного типа, так как при их использовании с lvalue-параметрами вывод типа после инстанцирования шаблона присвоит им значение lvalue-ссылки, которая по требованию языка обязана иметь инициализатор[6].

Склейка ссылок позволяет такие шаблоны:

class Obj {public:  template <class T>    Obj(T&& x) : field(std::forward<T>(x)) {}   // забежали вперёд и сделали правильноprivate:                                        // ниже объясним, почему без явной функции forward нельзя  std::string field;}

Для таких временных ссылок в компиляторах добавлены специальные правила[7], из-за чего…

  • если T=string, будет Obj(string&&)
  • если T=string&, будет Obj(string&)
  • если T=const string&, будет Obj(const string&)

Следствие: невозможно автоматически узнать, временная ли ссылка

Вернёмся к шаблонному конструктору Obj::Obj. Если не рассматривать посторонние типы, а только string, возможны три варианта.

  • T=string, инстанцируется в Obj(string&&), внутри x=string&.
  • T=string&, инстанцируется в Obj(string&), внутри x=string&.
  • T=const string&, инстанцируется в Obj(const string&), внутри x=const string&.

С третьим вариантом всё в порядке, но простым выведением типов невозможно отличить первый вариант от второго. А ведь в первом варианте для максимальной производительности нужен std::move, во втором он опасен: присваивание с перемещением «выпотрошит» строку, которая, возможно, ещё пригодится.

Решение: std::forward

Вернёмся к нашему шаблонному конструктору.

  template <class T>    Obj(T&& x) : field(std::forward<T>(x)) {}

Шаблон std::forward используется только в шаблонах (в нешаблонном коде хватает std::move). Он требует, чтобы тип был явно указан (иначе не отличишь Obj(string&&) от Obj(string&)), и либо ничего не делает, либо разворачивается в std::move.

Идиома «by-value + move»

Второй способ не множить сущности: параметр принимается по значению и передаётся дальше через std::move.

class Obj {public:  Obj(std::string x) : field(std::move(x)) {}private:  std::string field;}

Используется, когда перемещение объекта значительно «легче» копирования, обычно в нешаблонном коде.

Примечания

Источники

  • Д. Вандевурд, Н. Джосаттис, Д. Грегор. Шаблоны C++. Справочник разработчика = C++ Templates. The Complete Guide. — 2-е. — СПб. : «Альфа-книга», 2018. — 848 с. — ISBN 978-5-9500296-8-4.
  • I. Horton. Ivor Horton’s Beginning Visual C++ ® 2013. — John Wiley & Sons, Inc., 2014. — ISBN 978-1-118-84577-6.

Ссылки

🔥 Top keywords: Заглавная страницаЯндексДуров, Павел ВалерьевичСлужебная:ПоискYouTubeЛунин, Андрей АлексеевичПодносова, Ирина ЛеонидовнаВКонтактеФоллаут (телесериал)WildberriesTelegramРеал Мадрид (футбольный клуб)Богуславская, Зоя БорисовнаДуров, Валерий СемёновичРоссияXVideosСписок умерших в 2024 годуЧикатило, Андрей РомановичFallout (серия игр)Список игроков НХЛ, забросивших 500 и более шайбПопков, Михаил ВикторовичOzon17 апреляИльин, Иван АлександровичMail.ruСёгун (мини-сериал, 2024)Слово пацана. Кровь на асфальтеПутин, Владимир ВладимировичЛига чемпионов УЕФАГагарина, Елена ЮрьевнаБишимбаев, Куандык ВалихановичЛига чемпионов УЕФА 2023/2024Турнир претендентов по шахматам 2024Манчестер СитиMGM-140 ATACMSРоссийский миротворческий контингент в Нагорном КарабахеЗагоризонтный радиолокаторПинапВодительское удостоверение в Российской Федерации