Передача информации
- 1 year ago
- 0
- 0
Прямая передача ( англ. Perfect Forwarding ) — идиоматический механизм переноса атрибутов параметров в процедурах обобщённого кода языка C++ . Он был стандартизирован в редакции стандарта C++11 с помощью функционала библиотеки STL и синтаксиса передаваемыx ссылок ( англ. forwarding references ), а также унифицирован для применения совместно с вариативными шаблонами .
Прямая передача используется в тех случаях, когда от функций и процедур обобщённого кода требуется оставлять неизменными фундаментальные свойства своих параметризованных аргументов, то есть :
Практическое воплощение прямой передачи в стандарте языка реализовано с помощью функции
std::forward
из заголовочного файла
<utility>
. Вследствие чего, комбинация специальных правил вывода для
&&
-ссылок и их свёртки позволяет создать функциональный шаблон, который принимает произвольные аргументы с фиксацией их
типов
и основных свойств (
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, и вот почему.
Параметр string&& снаружи — временная (rvalue) ссылка, и передача именованного (lvalue) объекта невозможна. А внутри функции этот параметр именованный (lvalue), то есть string&. Сделано это для ошибкобезопасности: если в функции, принимающей string&&, идут сложные манипуляции с данными, невозможно случайно уничтожить параметр-string&&.
Вопросы начинаются, когда параметров много — приходится делать 4, 8, 16… конструкторов.
class Obj2 {
public:
Obj(const std::string& x1, const std::string& x2) : field1(x1), field2(x2) {}
Obj(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
-ссылки, которая по требованию языка обязана иметь инициализатор
.
Склейка ссылок позволяет такие шаблоны:
class Obj {
public:
template <class T>
Obj(T&& x) : field(std::forward<T>(x)) {} // забежали вперёд и сделали правильно
private: // ниже объясним, почему без явной функции forward нельзя
std::string field;
}
Для таких временных ссылок в компиляторах добавлены специальные правила , из-за чего…
Obj
(
string
&&
)
Obj
(
string
&
)
Obj
(
const
string
&
)
Вернёмся к шаблонному конструктору Obj::Obj. Если не рассматривать посторонние типы, а только string, возможны три варианта.
Obj
(
string
&&
)
, внутри x=string&.
Obj
(
string
&
)
, внутри x=string&.
Obj
(
const
string
&
)
, внутри x=const string&.
С третьим вариантом всё в порядке, но простым выведением типов невозможно отличить первый вариант от второго. А ведь в первом варианте для максимальной производительности нужен std::move, во втором он опасен: присваивание с перемещением «выпотрошит» строку, которая, возможно, ещё пригодится.
Вернёмся к нашему шаблонному конструктору.
template <class T>
Obj(T&& x) : field(std::forward<T>(x)) {}
Шаблон
std
::
forward
используется только в шаблонах (в нешаблонном коде хватает
std
::
move
). Он требует, чтобы тип был явно указан (иначе не отличишь
Obj
(
string
&&
)
от
Obj
(
string
&
)
), и либо ничего не делает, либо разворачивается в
std
::
move
.
Второй способ не множить сущности: параметр принимается по значению и передаётся дальше через
std
::
move
.
class Obj {
public:
Obj(std::string x) : field(std::move(x)) {}
private:
std::string field;
}
Используется, когда перемещение объекта значительно «легче» копирования, обычно в нешаблонном коде.