Прямая передача () — идиоматический механизм передачи параметров-ссылок из одной функции в другую с сохранением константности (временная/обычная/константная) в шаблонах языка C++. Он был стандартизирован в редакции стандарта C++11 с помощью функциональности библиотеки STL и синтаксиса передаваемыx ссылок (), а также унифицирован для применения совместно с вариативными шаблонами.
Прямая передача используется в тех случаях, когда от функций и процедур обобщённого кода требуется оставлять неизменными фундаментальные свойства своих параметризованных аргументов, то есть: константный объект должен передаваться по константной ссылке, модифицируемый объект — по обычной, временный — по временной ссылке.
Практическое воплощение прямой передачи в стандарте языка реализовано с помощью функции std::forward из заголовочного файла <utility>std::forward C++ reference, вследствие чего комбинация специальных правил вывода для &&-ссылок и их свёртки позволяет создать функциональный шаблон, который принимает произвольные аргументы с фиксацией их типов и основных свойств (rvalue или lvalue). Сохранение этой информации предопределяет возможность передавать данные аргументы при вызове других функций и методов.
Предпосылки Особое поведение именованных объектов типа «временная ссылка» Рассмотрим простейший объект с двумя конструкторами — один копирует поле из 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, и вот почему.
Параметр снаружи — временная (rvalue) ссылка, и передача именованного (lvalue) объекта невозможна. А внутри функции этот объект именованный (lvalue), то есть . Сделано это для безопасности: если в функции, продлевающей жизнь , идут сложные манипуляции с данными, потерять в производительности, не перенеся , предпочтительнее, чем преждевременно опустошить .
Вопросы начинаются, когда параметров много — приходится делать 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» и метапрограммирование. Чтобы метапрограммирование работало, в Си++ добавлена…
Склейка ссылок Склейку (свёртку, коллапсирование) ссылок () лучше всего объяснит такой код. using One = int&&; using Two = One&; // тогда Two = int&
При переходе к передаваемым ссылкам выясняется не только тип переданного в функцию параметра, но также даётся оценка, является ли он rvalue или lvalue. Если переданный в функцию параметр является lvalue, то подставляемое значение тоже будет ссылкой на lvalue. При этом, отмечается, что объявление типа параметра шаблона в виде &&-ссылки может иметь интересные побочные эффекты. Например, проявляется необходимость явного указания инициализаторов для всех локальных переменных данного типа, так как при их использовании с lvalue-параметрами вывод типа после инстанцирования шаблона присвоит им значение lvalue-ссылки, которая по требованию языка обязана иметь инициализатор.
Склейка ссылок позволяет использовать такие шаблоны: class Obj { public: template <class T> Obj(T&& x) : field(std::forward<T>(x)) {} // забежали вперёд и сделали правильно private: // ниже объясним, почему без явной функции forward нельзя std::string field; }
Для таких временных ссылок в компиляторах добавлены специальные правила, из-за чего… если T=string, будет если T=string&, будет если T=const string&, будет
Следствие: невозможно автоматически узнать, временная ли ссылка Вернёмся к шаблонному конструктору Obj::Obj. Если не рассматривать посторонние типы, а только string, возможны три варианта. T=string, инстанцируется в , внутри x=string&. T=string&, инстанцируется в , внутри x=string&. T=const string&, инстанцируется в , внутри x=const string&.
С третьим вариантом всё в порядке, но простым выведением типов невозможно отличить первый вариант от второго. В первом варианте для максимальной производительности нужен std::move, во втором он опасен: конструктор перемещения опустошит строку, которая, возможно, ещё пригодится.
Решение: std::forward Вернёмся к нашему шаблонному конструктору. template <class T> Obj(T&& x) : field(std::forward<T>(x)) {}
Шаблон используется только в шаблонах (в нешаблонном коде хватает ). Он требует, чтобы тип был явно указан (иначе не отличишь от ), и либо ничего не делает, либо разворачивается в .
Идиома «by-value + move» Второй способ не множить сущности: параметр принимается по значению и передаётся дальше через .
class Obj { public: Obj(std::string x) : field(std::move(x)) {} private: std::string field; }
Используется для небольших легко перемещаемых объектов, обычно в нешаблонном коде.
Примечания
Источники
Ссылки Eli Bendersky Perfect forwarding and universal references in C++, 03 November 2014
Категория:Шаблоны C++ Категория:Нововведения стандарта C++11