c++ - универсальные - Пересылка возвращаемых значений. Требуется ли std:: forward?




с++ универсальные ссылки (2)

В зависимости от того, что передается этой функцией, это приводит к неопределенному поведению! Точнее, если вы передадите значение non-lvalue, то есть rvalue, этой функции, значение, на которое ссылается возвращенная ссылка, будет устаревшим.

Также T&& не является «универсальной ссылкой», хотя эффект несколько напоминает универсальную ссылку в том T что T можно вывести как T& или T const& . Проблемный случай - это когда он выводится как T : аргументы передаются как временные и умирают после возвращения функции, но прежде чем что-либо может получить ссылку на нее.

Использование std::forward<T>(x) ограничено, например, пересылкой объектов при вызове другой функции: то, что вошло как временное, выглядит как lvalue внутри функции. Использование std::forward<T>(x) позволяет x выглядеть временным, если оно входит как одно - и, таким образом, разрешить переход от x при создании аргумента вызываемой функции.

Когда вы возвращаете объект из функции, есть несколько сценариев, которые вы, возможно, захотите позаботиться, но ни один из них не включает std::forward() :

  • Если тип на самом деле является ссылкой, либо const либо не const , вы не хотите ничего делать с объектом и просто возвращаете ссылку.
  • Если все операторы return используют одну и ту же переменную, или все используют временную, можно использовать copy / move elision и будут использоваться на достойных компиляторах. Поскольку копирование / перемещение является оптимизацией, это не обязательно происходит.
  • Если всегда возвращается одна и та же локальная переменная или временная, ее можно перенести из нее, если есть конструктор перемещения, иначе объект будет скопирован.
  • Когда возвращаются переменные или когда возврат включает выражение, ссылки могут быть возвращены, но копирование / перемещение elision не будет работать, и также невозможно напрямую перейти от результата, если он не является временным. В этих случаях вам нужно использовать std::move() чтобы разрешить перемещение с локального объекта.

В большинстве этих случаев полученный тип - T и вы должны возвращать T а не T&& . Если T - тип lvalue, результат может не быть lvalue, однако, возможно, потребуется удалить ссылочную квалификацию из возвращаемого типа. В сценарии вы специально спросили о типе T

Я пишу библиотеку, которая обертывает множество функций и методов из другой библиотеки. Чтобы избежать совпадения возвращаемых значений, я применяю std::forward следующим образом:

template<class T>
T&& wrapper(T&& t) { 
   f(t);  // t passed as lvalue  
   return std::forward<T>(t);
}

f возвращает void и принимает T&& (или перегружен по достоверности). Wrapper всегда возвращает параметр wrappers, а возвращаемое значение должно сохранять нецензурность аргумента. Действительно ли мне нужно использовать std::forward return ? RVO делает его излишним? Означает ли факт, что это ссылка (R или L), делает ее излишней? Нужно ли это, если возврат не является последним оператором функции (внутри некоторого if)?

Это debatable, если wrapper() должен возвращать void или T&& , потому что у вызывающего есть доступ к оцениваемому значению через arg (который является ссылкой, R или L). Но в моем случае мне нужно вернуть значение, чтобы wrapper() можно было использовать в выражениях.

Это может быть неуместно в вопросе, но известно, что функции f не украдут из t , поэтому 1-е использование std::forward в f(std::forward<T>(t)) является излишним, и оно было удалено меня.

Я написал небольшой тест: https://gist.github.com/3910503

Тест показывает, что возвращение непредвиденного T - создает дополнительную копию в gcc48 и clang32 с -O3 (RVO не срабатывает).

Кроме того, я не смог получить плохое поведение от UB в:

auto&& tmp = wrapper(42); 

Это ничего не доказывает, потому что это неопределенное поведение (если это UB).


В случае, если вы знаете, что t не будет находиться в состоянии переезда после вызова f , ваши два несколько разумных варианта:

  • return std::forward<T>(t) с типом T&& , который позволяет избежать любой конструкции, но позволяет записывать, например auto&& ref = wrapper(42); , который оставляет ссылку на болтовню

  • return std::forward<T>(t) с типом T , который в худшем случае запрашивает конструкцию перемещения, когда параметр является rvalue - это позволяет избежать вышеупомянутой проблемы для prvalues, но потенциально крадет из xvalues

Во всех случаях вам нужен std::forward . Копирование elision не рассматривается, потому что t всегда является ссылкой.





perfect-forwarding