Я пытаюсь понять rvalue ссылки и переместить семантику C ++ 11.
В чем разница между этими примерами, и какой из них не будет делать векторные копии?
Первый пример
std::vector<int> return_vector(void)
{
std::vector<int> tmp {1,2,3,4,5};
return tmp;
}
std::vector<int> &&rval_ref = return_vector();
Второй пример
std::vector<int>&& return_vector(void)
{
std::vector<int> tmp {1,2,3,4,5};
return std::move(tmp);
}
std::vector<int> &&rval_ref = return_vector();
Третий пример
std::vector<int> return_vector(void)
{
std::vector<int> tmp {1,2,3,4,5};
return std::move(tmp);
}
std::vector<int> &&rval_ref = return_vector();
c++
c++11
move-semantics
rvalue-reference
c++-faq
тарантул
источник
источник
std::move()
создана постоянная «копия».std::move(expression)
ничего не создает, просто переводит выражение в xvalue. Никакие объекты не копируются или перемещаются в процессе оценкиstd::move(expression)
.Ответы:
Первый пример
Первый пример возвращает временный объект, который перехватывается
rval_ref
. Этот временный период будет продлен за пределыrval_ref
определения, и вы можете использовать его, как если бы вы поймали его по значению. Это очень похоже на следующее:за исключением того, что в моем переписывании вы явно не можете использовать
rval_ref
неконстантный способ.Второй пример
Во втором примере вы создали ошибку во время выполнения.
rval_ref
теперь содержит ссылку на разрушеннуюtmp
внутри функции. Если повезет, этот код сразу же потерпит крах.Третий пример
Ваш третий пример примерно эквивалентен вашему первому.
std::move
Наtmp
ненужно и может быть на самом деле производительность pessimization , как это будет препятствовать оптимизации возвращаемого значения.Лучший способ кодировать то, что вы делаете, это:
Лучшая практика
Т.е. так же, как и в C ++ 03.
tmp
неявно обрабатывается как r-значение в операторе return. Он будет либо возвращен с помощью оптимизации возвращаемого значения (без копирования, без перемещения), либо, если компилятор решит, что не может выполнить RVO, он будет использовать конструктор перемещения вектора для выполнения возврата . Только если RVO не выполняется и если возвращаемый тип не имеет конструктора перемещения, конструктор копирования будет использоваться для возврата.источник
return my_local;
. Многократные операторы возврата в порядке и не будут препятствовать RVO.move
не создает временный. Он переводит lvalue в xvalue, не делая копий, ничего не создавая, ничего не разрушая. Этот пример точно такой же, как если бы вы вернулись с помощью lvalue-reference и удалили егоmove
из строки возврата: в любом случае у вас есть свисающая ссылка на локальную переменную внутри функции, которая была разрушена.Ни один из них не скопирует, но второй будет ссылаться на уничтоженный вектор. Именованные rvalue ссылки почти никогда не существуют в обычном коде. Вы пишете это так, как написали бы копию на C ++ 03.
За исключением теперь, вектор перемещается. Пользователь класса не имеет дело с его ссылками RValue в подавляющем большинстве случаев.
источник
tmp
не перемещается вrval_ref
, но записываются непосредственно вrval_ref
использовании РВО (т.е. скопировать Пропуска). Существует различие междуstd::move
копией elision. Аstd::move
может все еще включать некоторые данные, которые будут скопированы; в случае вектора новый конструктор фактически создается в конструкторе копирования, и данные выделяются, но основная часть массива данных копируется только путем копирования указателя (по существу). Исключение копий позволяет избежать 100% всех копий.rval_ref
переменные создаются с помощью конструктора перемещенияstd::vector
. Нет никакого конструктора копирования, связанного как с / безstd::move
.tmp
рассматривается как RValue вreturn
заявлении в этом случае.Простой ответ заключается в том, что вы должны написать код для rvalue ссылок, как если бы вы использовали обычный код ссылок, и вы должны обращаться с ними одинаково мысленно 99% времени. Это включает в себя все старые правила возврата ссылок (т.е. никогда не возвращать ссылку на локальную переменную).
Если вы не пишете класс контейнера шаблона, который должен использовать преимущества std :: forward и иметь возможность написать универсальную функцию, которая принимает ссылки либо на lvalue, либо на rvalue, это более или менее верно.
Одним из больших преимуществ конструктора перемещения и назначения перемещения является то, что если вы определите их, компилятор может использовать их в тех случаях, когда RVO (оптимизация возвращаемого значения) и NRVO (именованная оптимизация возвращаемого значения) не будут вызваны. Это довольно много для эффективного возврата дорогостоящих объектов, таких как контейнеры и строки, из методов.
Теперь, когда вещи становятся интересными со ссылками на rvalue, вы можете также использовать их в качестве аргументов обычных функций. Это позволяет вам писать контейнеры с перегрузками как для константной ссылки (const foo & other), так и для rvalue ссылки (foo && other). Даже если аргумент слишком громоздкий, чтобы передать его простым вызовом конструктора, это все равно можно сделать:
Контейнеры STL были обновлены, чтобы иметь перегрузки перемещения почти для чего угодно (ключ и значения хеша, вставка вектора и т. Д.), И именно там вы их увидите больше всего.
Вы также можете использовать их для обычных функций, и, если вы предоставите только ссылочный аргумент rvalue, вы можете заставить вызывающую сторону создать объект и позволить функции выполнить перемещение. Это скорее пример, чем действительно хорошее использование, но в моей библиотеке рендеринга я назначил строку всем загруженным ресурсам, чтобы было легче увидеть, что каждый объект представляет в отладчике. Интерфейс примерно такой:
Это форма «дырявой абстракции», но она позволяет мне воспользоваться тем фактом, что мне пришлось создавать строку уже большую часть времени, и избежать повторного ее копирования. Это не совсем высокопроизводительный код, но хороший пример возможностей, когда люди знакомятся с этой функцией. Этот код фактически требует, чтобы переменная была либо временной для вызова, либо вызываемой std :: move:
или
или
но это не скомпилируется!
источник
Не ответ как таковой , но руководство. В большинстве случаев нет смысла объявлять локальную
T&&
переменную (как вы это делалиstd::vector<int>&& rval_ref
). Вам все равно придетсяstd::move()
их использовать вfoo(T&&)
методах типа. Существует также проблема, о которой уже упоминалось, что при попытке вернуть ееrval_ref
из функции вы получите стандартную ссылку на уничтоженное временное фиаско.Большую часть времени я бы использовал следующую схему:
У вас нет ссылок на возвращенные временные объекты, поэтому вы избегаете (неопытной) ошибки программиста, который хочет использовать перемещенный объект.
Очевидно, что есть (хотя и довольно редко) случаи, когда функция действительно возвращает a,
T&&
который является ссылкой на невременный объект, который вы можете переместить в ваш объект.Что касается RVO: эти механизмы обычно работают, и компилятор может избежать копирования, но в случаях, когда путь возврата неочевиден (исключения,
if
условия, определяющие именованный объект, который вы вернете, и, вероятно, пара других), rrefs - ваши спасители (даже если потенциально больше дорогой).источник
Ни один из них не будет делать никакого дополнительного копирования. Даже если RVO не используется, новый стандарт гласит, что конструкцию перемещения предпочтительнее копировать при выполнении возвратов, как мне кажется.
Я верю, что ваш второй пример вызывает неопределенное поведение, потому что вы возвращаете ссылку на локальную переменную.
источник
Как уже упоминалось в комментариях к первому ответу,
return std::move(...);
конструкция может иметь значение в случаях, отличных от возврата локальных переменных. Вот работающий пример, который документирует, что происходит, когда вы возвращаете объект члена с и безstd::move()
:Предположительно,
return std::move(some_member);
имеет смысл только в том случае, если вы действительно хотите переместить конкретного члена класса, например, в случае, когдаclass C
представляет недолговечные объекты адаптера с единственной целью создания экземпляровstruct A
.Обратите внимание , как
struct A
всегда получает скопировано изclass B
, даже еслиclass B
объект является R-значение. Это потому, что компилятор не может сказать, что этотclass B
экземплярstruct A
больше не будет использоваться. Вclass C
компилятор имеет эту информациюstd::move()
, из -за чегоstruct A
становится перемещен , если экземплярclass C
не является постоянным.источник