Если вы читаете код, как
auto&& var = foo();
где foo
любая функция, возвращаемая по значению типа T
. Тогда var
lvalue типа rvalue ссылается на T
. Но для чего это нужно var
? Значит ли это, что нам разрешено воровать ресурсы var
? Существуют ли разумные ситуации, когда вы должны использовать, auto&&
чтобы сообщить читателю своего кода что-то подобное, когда вы возвращаете a, unique_ptr<>
чтобы сообщить, что у вас есть исключительное право собственности? А что, например, T&&
когда T
класс?
Я просто хочу понять, есть ли другие варианты использования, отличные от auto&&
тех, которые используются в программировании шаблонов; как те, которые обсуждались в примерах в этой статье Универсальные ссылки Скотта Мейерса.
auto&&
? Я думал о том, чтобы рассмотреть, почему основанный на диапазоне цикл for расширяется, чтобы использовать егоauto&&
в качестве примера, но до сих пор не дошел до этого. Возможно, тот, кто ответит, сможет это объяснить.foo
возврата, сохраняя значение r в нем, звучит как UB для ne.foo
может, например , выглядеть следующим образом :int foo(){return 1;}
.Ответы:
При использовании
auto&& var = <initializer>
вы говорите: я приму любой инициализатор независимо от того, является ли это выражением lvalue или rvalue, и я сохраню его константу . Это обычно используется для пересылки (обычно сT&&
). Причина, по которой это работает, заключается в том, что «универсальная ссылка»auto&&
илиT&&
будет привязана к чему-либо .Вы можете сказать, ну почему бы просто не использовать a,
const auto&
потому что это также будет связывать что-либо? Проблема с использованиемconst
ссылки в том, что этоconst
! Вы не сможете позже связать его с любыми неконстантными ссылками или вызывать любые функции-члены, которые не отмеченыconst
.В качестве примера представьте, что вы хотите получить a
std::vector
, перевести итератор к его первому элементу и каким-то образом изменить значение, на которое указывает этот итератор:Этот код будет прекрасно компилироваться независимо от выражения инициализатора. Альтернативы
auto&&
потерпеть неудачу следующими способами:Так что для этого
auto&&
отлично работает! Примером такого использованияauto&&
являетсяfor
цикл на основе диапазона . Смотрите мой другой вопрос для более подробной информации.Если затем
std::forward
на вашейauto&&
ссылку , чтобы сохранить тот факт , что он был первоначально либо именующим или Rvalue, ваш код говорит: Теперь, когда я получил свой объект либо от Lvalue или RValue выражения, я хочу , чтобы сохранить зависимости от того , valueness его первоначально так что я могу использовать его наиболее эффективно - это может сделать его недействительным. Как в:Это позволяет
use_it_elsewhere
вырывать его изнутри ради производительности (избегая копий), когда исходный инициализатор был изменяемым значением.Что это означает относительно того, можем ли мы или когда мы можем красть ресурсы
var
? Ну, посколькуauto&&
воля к чему-либо привязана, мы не можем попытаться вырватьvar
внутренности - это вполне может быть lvalue или даже const. Мы можем однакоstd::forward
это к другим функциям, которые могут полностью разрушить его внутренности. Как только мы сделаем это, мы должныvar
быть в недопустимом состоянии.Теперь давайте применим это к случаю
auto&& var = foo();
, как указано в вашем вопросе, где foo возвращаетT
значение по. В этом случае мы точно знаем, что типvar
будет выведен какT&&
. Так как мы точно знаем, что это ценность, нам не нужноstd::forward
разрешение на кражу его ресурсов. В этом конкретном случае, зная, чтоfoo
возвращается по значению , читатель должен просто прочитать его как: Я беру ссылку на rvalue на возвращаемое временное значениеfoo
, так что я могу с радостью отказаться от него.В качестве дополнения, я думаю, стоит упомянуть, когда
some_expression_that_may_be_rvalue_or_lvalue
может появиться такое выражение , кроме ситуации «хорошо, ваш код может измениться». Итак, вот надуманный пример:Вот
get_vector<T>()
то прекрасное выражение, которое может быть lvalue или rvalue в зависимости от универсального типаT
. Мы существенно изменим тип возвращаемого значенияget_vector
через параметр шаблонаfoo
.Когда мы позвоним
foo<std::vector<int>>
,get_vector
вернемсяglobal_vec
по значению, которое дает выражение rvalue. В качестве альтернативы, когда мы вызываемfoo<std::vector<int>&>
,get_vector
мы вернемсяglobal_vec
по ссылке, что приведет к выражению lvalue.Если мы делаем:
Как и ожидалось, мы получаем следующий вывод:
Если вы должны были изменить
auto&&
в коде любой изauto
,auto&
,const auto&
илиconst auto&&
мы не получим результат , который мы хотим.Альтернативный способ изменить логику программы в зависимости от того
auto&&
, инициализирована ли ваша ссылка выражением lvalue или rvalue, - использовать черты типа:источник
T vec = get_vector<T>();
внутри функции foo? Или я упрощаю это до абсурдного уровня :)Во-первых, я рекомендую прочитать этот мой ответ в качестве дополнительного руководства для пошагового объяснения того, как работает вывод аргумента шаблона для универсальных ссылок.
Не обязательно. Что, если
foo()
вдруг вернется ссылка, или вы изменили вызов, но забыли обновить использованиеvar
? Или, если вы используете общий код и тип возвращаемого значенияfoo()
может меняться в зависимости от ваших параметров?Думайте о том,
auto&&
чтобы быть точно таким же, какT&&
вtemplate<class T> void f(T&& v);
, потому что это (почти † ) именно это. Что вы делаете с универсальными ссылками в функциях, когда вам нужно передать их или использовать каким-либо образом? Вы используете,std::forward<T>(v)
чтобы вернуть исходную категорию значения. Если это было lvalue до того, как оно было передано вашей функции, оно остается lvalue после прохождения через негоstd::forward
. Если это было значение rvalue, оно снова станет значением rvalue (помните, что именованная ссылка на rvalue является lvalue).Итак, как вы используете
var
правильно в общем виде? Использованиеstd::forward<decltype(var)>(var)
. Это будет работать точно так же, какstd::forward<T>(v)
в шаблоне функции выше. Еслиvar
этоT&&
, вы получите обратно значение, а если это такT&
, вы получите обратно значение.Итак, вернемся к теме: что говорят нам
auto&& v = f();
иstd::forward<decltype(v)>(v)
в кодовой базе? Они говорят нам, чтоv
будут приобретены и переданы самым эффективным способом. Помните, однако, что после пересылки такой переменной вполне возможно, что она перемещена, поэтому было бы неправильно использовать ее дальше без сброса.Лично я использую
auto&&
в общем коде, когда мне нужна изменяемая переменная. Идеальная переадресация значения r является модифицирующей, поскольку операция перемещения потенциально крадет у нее внутренности. Если я просто хочу быть ленивым (т. Е. Не записывать имя типа, даже если я его знаю) и мне не нужно изменять (например, при печати элементов диапазона), я буду придерживатьсяauto const&
.†
auto
в настолько сильно отличается , чтоauto v = {1,2,3};
сделает , в то время как будет провал вычет.v
std::initializer_list
f({1,2,3})
источник
foo()
возвращает тип значенияT
, тоvar
(это выражение) будет lvalue, а его тип (этого выражения) будет ссылкой на rvalueT
(то естьT&&
).Рассмотрим некоторый тип,
T
который имеет конструктор перемещения, и предположим,использует этот конструктор перемещения.
Теперь давайте воспользуемся промежуточной ссылкой для захвата возврата из
foo
:это исключает использование конструктора перемещения, поэтому возвращаемое значение нужно будет копировать, а не перемещать (даже если мы используем
std::move
здесь, мы не можем на самом деле перемещаться через const ref)Тем не менее, если мы используем
конструктор перемещения все еще доступен.
И чтобы ответить на ваши другие вопросы:
Первое, что, как говорит Xeo, это то, что, по сути, я передаю X настолько эффективно, насколько это возможно , независимо от типа X. Таким образом, видя код, который использует
auto&&
внутренне, следует сообщить, что он будет использовать семантику перемещения внутри, где это необходимо.Когда шаблон функции принимает аргумент типа
T&&
, он говорит, что может переместить объект, который вы передаете. Возвращениеunique_ptr
явно дает право собственности вызывающей стороне; ПринятиеT&&
может отменить право собственности у вызывающего абонента (если cort существует и т. д.).источник
ref
иrvref
оба lvalues. Если вам нужен конструктор перемещения, тогда вы должны написатьT t(std::move(rvref))
.auto const &
:?auto&&
и что вы говорите читателю своего кода с помощьюauto&&
?auto &&
Синтаксис использует два новых возможностей C ++ 11:Эта
auto
часть позволяет компилятору определять тип на основе контекста (в данном случае возвращаемого значения). Это без каких-либо справочных квалификаций (что позволяет указать, хотите ли выT
,T &
илиT &&
для выведенного типаT
).Это
&&
новый ход семантики. Семантика перемещения, поддерживающая тип, реализует конструктор,T(T && other)
который оптимально перемещает содержимое в новый тип. Это позволяет объекту менять внутреннее представление вместо выполнения глубокой копии.Это позволяет вам иметь что-то вроде:
Так:
выполнит копию возвращенного вектора (дорого), но:
поменяет местами внутреннее представление вектора (вектор from
foo
и пустой вектор fromvar
), поэтому будет быстрее.Это используется в новом синтаксисе цикла for:
Где цикл for хранит
auto &&
возвращаемое значениеfoo
иitem
является ссылкой на каждое значение вfoo
.источник
auto&&
не будет ничего перемещать, он просто сделает ссылку. Является ли это ссылкой на lvalue или rvalue, зависит от выражения, использованного для его инициализации.std::vector
иstd::string
являются переставляемыми. Это не имеет ничего общего с типомvar
.