Что означает авто & amp; Расскажи нам?

Если вы читаете код, как

auto&& var = foo();

где foo любая функция, возвращаемая по значению типа T, затем var является lvalue типа rvalue ссылки на T, Но что это значит для var? Означает ли это, что мы можем украсть ресурсы var? Есть ли разумные ситуации, когда вы должны использовать auto&& сказать читателю вашего кода что-то подобное, когда вы возвращаете unique_ptr<> сказать, что у вас есть эксклюзивная собственность? А как насчет например T&& когда T имеет тип класса?

Я просто хочу понять, есть ли другие случаи использования auto&& чем те, в шаблонном программировании; как те, которые обсуждались в примерах в этой статье Универсальные ссылки Скотт Мейерс.

123

Решение

Используя auto&& var = <initializer> ты говоришь: Я приму любой инициализатор независимо от того, является ли он выражением lvalue или rvalue, и я сохраню его константу. Это обычно используется для пересылка (обычно с T&&). Причина, по которой это работает, заключается в том, что «универсальная ссылка», auto&& или же T&&будет привязывать к что-нибудь.

Вы можете сказать, ну почему бы просто не использовать const auto& потому что это будет также привязать к чему-либо? Проблема с использованием const Ссылка в том, что это const! Вы не сможете позже связать его с любыми неконстантными ссылками или вызвать любые функции-члены, которые не помечены const,

В качестве примера представьте, что вы хотите получить std::vectorВозьмите итератор к его первому элементу и измените значение, на которое указывает этот итератор:

auto&& vec = some_expression_that_may_be_rvalue_or_lvalue;
auto i = std::begin(vec);
(*i)++;

Этот код будет прекрасно компилироваться независимо от выражения инициализатора. Альтернативы auto&& потерпеть неудачу следующими способами:

auto         => will copy the vector, but we wanted a reference
auto&        => will only bind to modifiable lvalues
const auto&  => will bind to anything but make it const, giving us const_iterator
const auto&& => will bind only to rvalues

Так что для этого, auto&& работает отлично! Пример использования auto&& как это в диапазоне на основе for петля. Увидеть мой другой вопрос Больше подробностей.

Если вы тогда используете std::forward на ваше auto&& ссылка для сохранения того факта, что изначально она была либо lvalue, либо rvalue, ваш код говорит: Теперь, когда я получил ваш объект из выражения lvalue или rvalue, я хочу сохранить значение, которое у него было изначально, чтобы я мог использовать его наиболее эффективно — это может сделать его недействительным. Как в:

auto&& var = some_expression_that_may_be_rvalue_or_lvalue;
// var was initialized with either an lvalue or rvalue, but var itself
// is an lvalue because named rvalues are lvalues
use_it_elsewhere(std::forward<decltype(var)>(var));

Это позволяет use_it_elsewhere вырвать его внутренности ради производительности (избегая копий), когда исходный инициализатор был изменяемым значением.

Что это означает относительно того, можем ли мы или когда мы можем украсть ресурсы у var? Ну так как auto&& будет привязан к чему-либо, мы не можем попытаться вырвать vars s внутренности — это вполне может быть lvalue или даже const. Мы можем однако std::forward это к другим функциям, которые могут полностью разрушить его внутренности. Как только мы сделаем это, мы должны рассмотреть var находиться в недопустимом состоянии.

Теперь давайте применим это к случаю auto&& var = foo();, как указано в вашем вопросе, где foo возвращает T по значению. В этом случае мы точно знаем, что тип var будет выведено как T&&, Поскольку мы точно знаем, что это значение, нам не нужно std::forwardразрешение украсть его ресурсы. В этом конкретном случае знаю это foo возвращается по значению, читатель должен просто прочитать это как: Я беру ссылку на временное возвращение из fooтак что я могу с радостью отойти от этого.


В качестве дополнения, я думаю, что стоит упомянуть, когда такое выражение, как some_expression_that_may_be_rvalue_or_lvalue может появиться, кроме ситуации «хорошо, ваш код может измениться». Итак, вот надуманный пример:

std::vector<int> global_vec{1, 2, 3, 4};

template <typename T>
T get_vector()
{
return global_vec;
}

template <typename T>
void foo()
{
auto&& vec = get_vector<T>();
auto i = std::begin(vec);
(*i)++;
std::cout << vec[0] << std::endl;
}

Вот, 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.

Если мы делаем:

foo<std::vector<int>>();
std::cout << global_vec[0] << std::endl;
foo<std::vector<int>&>();
std::cout << global_vec[0] << std::endl;

Мы получаем следующий результат, как и ожидалось:

2
1
2
2

Если бы вы должны были изменить auto&& в коде к любому из auto, auto&, const auto&, или же const auto&& тогда мы не получим желаемого результата.


Альтернативный способ изменить логику программы в зависимости от того, auto&& ссылка инициализируется выражением lvalue или rvalue для использования черт типа:

if (std::is_lvalue_reference<decltype(var)>::value) {
// var was initialised with an lvalue expression
} else if (std::is_rvalue_reference<decltype(var)>::value) {
// var was initialised with an rvalue expression
}
173

Другие решения

Во-первых, я рекомендую прочитать этот мой ответ как подробное описание пошагового объяснения того, как работает вывод аргумента шаблона для универсальных ссылок.

Означает ли это, что мы можем украсть ресурсы var?

Не обязательно. Что, если 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&Вы получите обратно lvalue.

Итак, вернемся к теме: что делать auto&& v = f(); а также std::forward<decltype(v)>(v) в кодовой базе скажите нам? Они говорят нам, что v будут приобретены и переданы наиболее эффективным способом. Помните, однако, что после пересылки такой переменной, возможно, что она перемещена, поэтому было бы неправильно использовать ее дальше без сброса.

Лично я пользуюсь auto&& в общем коде, когда мне нужно modifyable переменная. Совершенная пересылка значения r является изменяющей, так как операция перемещения потенциально может похитить его. Если я просто хочу быть ленивым (то есть не пишу имя типа, даже если я его знаю) и не нуждаюсь в изменении (например, при печати только элементов диапазона), я буду придерживаться auto const&,


auto настолько отличается, что auto v = {1,2,3}; сделаю v std::initializer_list, в то время как f({1,2,3}) будет отказ от удержания.

11

Рассмотрим какой-то тип T который имеет конструктор перемещения, и предположим,

T t( foo() );

использует этот конструктор перемещения.

Теперь давайте использовать промежуточную ссылку для захвата возврата из foo:

auto const &ref = foo();

это исключает использование конструктора перемещения, поэтому возвращаемое значение придется копировать, а не перемещать (даже если мы используем std::move здесь мы не можем на самом деле двигаться через const ref)

T t(std::move(ref));   // invokes T::T(T const&)

Тем не менее, если мы используем

auto &&rvref = foo();
// ...
T t(std::move(rvref)); // invokes T::T(T &&)

Конструктор перемещения все еще доступен.


И чтобы ответить на ваши другие вопросы:

… Есть ли разумные ситуации, когда вы должны использовать авто&& рассказать читателю вашего кода что-то …

Первое, что, как говорит Xeo, это Я прохожу X максимально эффективно, какой бы тип X не был. Итак, увидев код, который использует auto&& внутренне следует сообщить, что она будет использовать семантику перемещения внутри, где это уместно.

… как вы делаете, когда вы возвращаете unique_ptr<> Сказать, что у вас есть исключительная собственность …

Когда шаблон функции принимает аргумент типа T&&, он говорит, что может переместить объект, который вы передаете. Возвращение unique_ptr явно дает право собственности вызывающей стороне; принимающий T&& может Удалить владение от вызывающего абонента (если cort существует и т. д.).

2

auto && синтаксис использует две новые функции C ++ 11:

  1. auto part позволяет компилятору определять тип на основе контекста (в данном случае возвращаемого значения). Это без каких-либо справочных квалификаций (позволяя вам указать, хотите ли вы T, T & или же T && для выведенного типа T).

  2. && это новый ход семантики. Тип, поддерживающий семантику перемещения, реализует конструктор T(T && other) это оптимально перемещает контент в новом типе. Это позволяет объекту поменять местами внутреннее представление вместо глубокого копирования.

Это позволяет вам иметь что-то вроде:

std::vector<std::string> foo();

Так:

auto var = foo();

выполнит копию возвращенного вектора (дорого), но:

auto &&var = foo();

поменяет внутреннее представление вектора (вектор из foo и пустой вектор из var), так будет быстрее.

Это используется в новом синтаксисе цикла for:

for (auto &item : foo())
std::cout << item << std::endl;

Где цикл for держит auto && на возвращаемое значение из foo а также item ссылка на каждое значение в foo,

-3