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



В продолжение к предыдущему посту.



Мы теперь знаем, что универсальные ссылки могут работать с разными категориями выражений lvalue и xvalue. При написании кода шаблонной функции мы можем не знать, какие аргументы могут быть переданы в неё. Соответственно, мы не знаем, можем ли мы распоряжаться её внутренними ресурсами. Всё это сильно влияет на производительность нашего решения. Что же делать в такой ситуации?



Конечно, как вы уже знаете, мы можем детектировать тип rvalue reference. И да, мы можем написать два разных участка кода для двух разных категорий выражений. Можно, но нужно ли? Это противоречит дублированию кода.



Функция std::forward используется для так называемой идеальной передачи аргументов при вызове других методов, конструкторов и функций:

template<typename T>

void foo(T &&message)

{

T tmp(std::forward<T>(message));

...

}




В данном примере во временный объект tmp будет передано либо lvalue, либо xvalue. Следовательно, мы либо скопируем строку, либо переместим. Это зависит от того, как вызвали foo:

std::string str = "blah blah blah";



// Передает lvalue => std::string tmp(str);

foo(str);



// Передает xvalue => std::string tmp(std::move(str));

foo(std::move(str));




То есть std::forward выполняет проброс информации о категории выражения внутрь. Отсюда и название: forward, т.е. дальше.



Отлично, где же нам такая радость может пригодиться? Конечно же, при использовании универсальных ссылок. В основном, при написании оберток над чем-то.



Пример I. Инициализация объекта по универсальной ссылке:

template<class T>

class wrapper

{

std::vector<T> m_data;

public:

template<class Y>

wrapper(Y &&data)

: m_data(std::forward<Y>(data))

{

// make a copy from `data` or move resources from `data`

}

};




Пример II. При работе с контейнерами STL я предпочитаю использовать семейство функций emplace, т.к. они предоставляют возможность сконструировать объект сразу там, где он будет потом храниться. В основе таких методов лежит std::forward, который пробрасывает аргументы вплоть до конструкторов. Смотрите сами тут.



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



Не забываем об исключениях в перемещающем конструкторе, а так же про оптимизации RVO/NRVO.



#cppcore #memory #algorithm