Я только что закончил слушать радио- интервью подкаста Software Engineering со Скоттом Мейерсом о C ++ 0x . Большинство новых функций имело смысл для меня, и сейчас я на самом деле рад C ++ 0x, за исключением одного. Я до сих пор не понимаю семантику перемещения ... Что это такое?
c++
c++-faq
c++11
move-semantics
dicroce
источник
источник
Ответы:
Мне проще понять семантику перемещения с помощью примера кода. Давайте начнем с очень простого строкового класса, который содержит только указатель на выделенный в куче блок памяти:
Поскольку мы решили сами управлять памятью, нам нужно следовать правилу трех . Я собираюсь отложить написание оператора присваивания и пока реализовать только деструктор и конструктор копирования:
Конструктор копирования определяет, что значит копировать строковые объекты. Параметр
const string& that
привязывается ко всем выражениям типа string, что позволяет вам делать копии в следующих примерах:Теперь приходит ключ к пониманию семантики перемещения. Обратите внимание, что только в первой строке, где мы копируем
x
, эта глубокая копия действительно необходима, потому что мы могли бы захотеть проверитьx
позже и были бы очень удивлены, если быx
что-то изменилось. Вы заметили, как я только что сказалx
три раза (четыре раза, если вы включите это предложение) и имел в виду один и тот же объект каждый раз? Мы называем такие выражения, какx
«lvalues».Аргументы в строках 2 и 3 - это не lvalues, а rvalues, потому что нижележащие строковые объекты не имеют имен, поэтому у клиента нет возможности проверить их снова в более поздний момент времени. Значения r обозначают временные объекты, которые уничтожаются в следующей точке с запятой (точнее: в конце полного выражения, которое лексически содержит значение r). Это важно, потому что во время инициализации
b
иc
мы могли делать все, что хотели с исходной строкой, и клиент не мог сказать разницу !C ++ 0x представляет новый механизм, называемый «ссылкой на rvalue», который, помимо прочего, позволяет обнаруживать аргументы rvalue через перегрузку функций. Все, что нам нужно сделать, это написать конструктор со ссылочным параметром rvalue. Внутри этого конструктора мы можем делать с источником все, что захотим , при условии, что мы оставляем его в каком-то допустимом состоянии:
Что мы здесь сделали? Вместо глубокого копирования данных кучи, мы просто скопировали указатель и затем установили исходный указатель на ноль (чтобы не допустить «delete []» из деструктора исходного объекта для освобождения наших «только что украденных данных»). По сути, мы «украли» данные, которые изначально принадлежали исходной строке. Опять же, ключевой момент заключается в том, что ни при каких обстоятельствах клиент не может обнаружить, что источник был изменен. Поскольку мы не делаем здесь копию, мы называем этот конструктор «конструктором перемещения». Его задача - перемещать ресурсы с одного объекта на другой, а не копировать их.
Поздравляем, теперь вы понимаете основы семантики перемещения! Давайте продолжим, реализовав оператор присваивания. Если вы не знакомы с идиомой копирования и обмена , изучите ее и возвращайтесь, потому что это потрясающая идиома C ++, связанная с безопасностью исключений.
Да это все? "Где ссылка? Вы можете спросить. "Нам здесь не нужно!" мой ответ :)
Обратите внимание, что мы передаем параметр
that
по значению , поэтомуthat
его нужно инициализировать, как и любой другой строковый объект. Как именноthat
будет инициализироваться? В былые времена C ++ 98 ответом был бы «конструктор копирования». В C ++ 0x компилятор выбирает между конструктором копирования и конструктором перемещения в зависимости от того, является ли аргумент оператора присваивания lvalue или rvalue.Поэтому, если вы скажете
a = b
, конструктор копирования будет инициализированthat
(потому что выражениеb
является lvalue), а оператор присваивания заменяет содержимое только что созданной глубокой копией. Это само определение копии и идиома замены - создайте копию, обменяйте содержимое копией, а затем избавьтесь от копии, покинув область действия. Здесь нет ничего нового.Но если вы скажете
a = x + y
, конструктор перемещения будет инициализированthat
(потому что выражениеx + y
является r-значением), так что здесь не требуется глубокая копия, а только эффективное перемещение.that
все еще является независимым объектом от аргумента, но его построение было тривиальным, так как данные кучи не нужно было копировать, просто перемещать. Не было необходимости копировать его, потому что оноx + y
является rvalue, и, опять же, можно перейти от строковых объектов, обозначенных rvalue.Подводя итог, конструктор копирования делает глубокую копию, потому что источник должен оставаться нетронутым. С другой стороны, конструктор перемещения может просто скопировать указатель, а затем установить нулевой указатель в источнике. Можно «обнулить» исходный объект таким образом, потому что у клиента нет способа снова проверить объект.
Я надеюсь, что этот пример объяснил главное. Существует намного больше, чтобы ценить ссылки и перемещать семантику, которую я намеренно оставил для простоты. Если вы хотите получить более подробную информацию, пожалуйста, смотрите мой дополнительный ответ .
источник
that.data = 0
, персонажи будут уничтожены слишком рано (когда умрет временный), а также дважды. Вы хотите украсть данные, а не делиться ими!delete[]
определяется стандартом C ++ как неиспользуемый .Моим первым ответом было предельно упрощенное введение в перемещение семантики, и многие детали были упущены с целью упростить его. Тем не менее, есть еще много чего изменить семантику, и я подумал, что пришло время для второго ответа, чтобы заполнить пробелы. Первый ответ уже довольно старый, и было бы неправильно просто заменить его совершенно другим текстом. Я думаю, что это все еще служит хорошим введением. Но если вы хотите копать глубже, читайте дальше :)
Стефан Т. Лававей нашел время, чтобы дать ценные отзывы. Большое спасибо, Стефан!
Введение
Семантика перемещения позволяет объекту при определенных условиях вступать во владение внешними ресурсами какого-либо другого объекта. Это важно двумя способами:
Превращение дорогих копий в дешевые ходы. Смотрите мой первый ответ для примера. Обратите внимание, что если объект не управляет хотя бы одним внешним ресурсом (напрямую или косвенно через свои объекты-члены), семантика перемещения не даст никаких преимуществ по сравнению с семантикой копирования. В этом случае копирование объекта и перемещение объекта означают одно и то же:
Реализация безопасных типов «только для перемещения»; то есть типы, для которых копирование не имеет смысла, но перемещение имеет смысл. Примеры включают в себя блокировки, файловые дескрипторы и интеллектуальные указатели с уникальной семантикой владения. Примечание. В этом ответе обсуждается
std::auto_ptr
устаревший шаблон стандартной библиотеки C ++ 98, который был заменен наstd::unique_ptr
C ++ 11. Программисты среднего уровня C ++, вероятно, хотя бы немного знакомы с нимstd::auto_ptr
, и из-за отображаемой им «семантики перемещения» это кажется хорошей отправной точкой для обсуждения семантики перемещения в C ++ 11. YMMV.Что такое ход?
Стандартная библиотека C ++ 98 предлагает интеллектуальный указатель с уникальной семантикой владения
std::auto_ptr<T>
. В случае, если вы не знакомыauto_ptr
, его цель - гарантировать, что динамически размещаемый объект всегда освобождается, даже при исключениях:Необычная вещь о
auto_ptr
его "копирующем" поведении:Обратите внимание , как инициализация
b
сa
вовсе не копировать треугольник, но вместо этого передает право собственности на треугольнике отa
доb
. Мы также говорят , что «a
будет перемещен вb
» или «треугольник перемещается изa
кb
». Это может показаться странным, потому что сам треугольник всегда остается в памяти в одном месте.Конструктор копирования,
auto_ptr
вероятно, выглядит примерно так (несколько упрощенно):Опасные и безобидные ходы
Опасность в том,
auto_ptr
что то, что синтаксически выглядит как копия, на самом деле является движением. Попытка вызова функции-члена в Move-fromauto_ptr
вызовет неопределенное поведение, поэтому вы должны быть очень осторожны, чтобы не использовать функциюauto_ptr
после ее перемещения из:Но
auto_ptr
это не всегда опасно. Заводские функции - прекрасный вариант использования дляauto_ptr
:Обратите внимание, что оба примера следуют одному и тому же синтаксическому шаблону:
И все же один из них вызывает неопределенное поведение, тогда как другой - нет. Так в чем же разница между выражениями
a
иmake_triangle()
? Разве они не одного типа? На самом деле они есть, но у них есть разные категории стоимости .Категории значений
Очевидно, что между выражением,
a
которое обозначаетauto_ptr
переменную, и выражением,make_triangle()
которое обозначает вызов функции, возвращающейauto_ptr
значение by , должно быть какое-то глубокое различие , создавая тем самым свежий временныйauto_ptr
объект каждый раз, когда он вызывается.a
является примером lvalue , тогда какmake_triangle()
является примером rvalue .Переход от значений l, таких как
a
опасный, потому что позже мы можем попытаться вызвать функцию-членa
, вызывая неопределенное поведение. С другой стороны, переход от значений r, таких какmake_triangle()
совершенно безопасный, потому что после того, как конструктор копирования выполнил свою работу, мы не можем снова использовать временные. Нет выражения, которое обозначает временное; если мы просто напишемmake_triangle()
снова, мы получим другой временный. Фактически, перемещенный из временного уже ушел на следующую строку:Обратите внимание, что буквы
l
иr
имеют историческое происхождение в левой и правой части задания. Это больше не верно в C ++, потому что есть l-значения, которые не могут появляться в левой части присваивания (например, массивы или пользовательские типы без оператора присваивания), и есть r-значения, которые могут (все r-значения типов классов). с оператором присваивания).Rvalue ссылки
Теперь мы понимаем, что переход от lvalues потенциально опасен, но переход от rvalues безвреден. Если бы в C ++ была языковая поддержка, чтобы отличать аргументы lvalue от аргументов rvalue, мы могли бы либо полностью запретить переход от lvalue, либо, по крайней мере, сделать переход от lvalue явным на сайте вызова, чтобы мы больше не перемещались случайно.
Ответ C ++ 11 на эту проблему - rvalue ссылки . Ссылка на rvalue - это новый вид ссылок, который привязывается только к rvalue, а синтаксис - это
X&&
. Старая добрая ссылкаX&
теперь называется ссылкой lvalue . (Обратите внимание, чтоX&&
это не ссылка на ссылку; такого нет в C ++.)Если мы добавим
const
в микс, у нас уже есть четыре разных типа ссылок. С какими типами выраженийX
они могут связываться?На практике вы можете забыть о
const X&&
. Ограничение чтения из значений не очень полезно.Неявные преобразования
Rvalue ссылки прошли через несколько версий. Начиная с версии 2.1, ссылка rvalue
X&&
также связывается со всеми категориями значений другого типаY
, при условии, что существует неявное преобразование изY
вX
. В этом случае создается временный типX
, и ссылка на rvalue привязывается к этому временному:В приведенном выше примере
"hello world"
это lvalue типаconst char[12]
. Поскольку существует неявное преобразование изconst char[12]
сквозногоconst char*
вstd::string
, создается временный типstd::string
иr
привязывается к этому временному объекту. Это один из случаев, когда различие между значениями (выражениями) и временными значениями (объектами) немного размыто.Переместить конструкторы
Полезный пример функции с
X&&
параметром - конструктор перемещенияX::X(X&& source)
. Его целью является передача права собственности на управляемый ресурс из источника в текущий объект.В C ++ 11
std::auto_ptr<T>
был заменен наstd::unique_ptr<T>
который использует ссылки на rvalue. Я буду разрабатывать и обсуждать упрощенную версиюunique_ptr
. Во- первых, мы инкапсулировать сырой указатель и перегружать операторы->
и*
, поэтому наш класс чувствует , как указатель:Конструктор становится владельцем объекта, а деструктор удаляет его:
Теперь перейдем к интересной части, конструктору перемещения:
Этот конструктор перемещения делает именно то, что
auto_ptr
сделал конструктор копирования, но он может быть предоставлен только с rvalues:Вторая строка не компилируется, потому что
a
это lvalue, но параметрunique_ptr&& source
может быть привязан только к rvalue. Это именно то, что мы хотели; опасные действия никогда не должны быть скрытыми. Третья строка компилируется просто отлично, потому чтоmake_triangle()
это значение. Конструктор перемещения переведет владение из временного объекта вc
. Опять же, это именно то, что мы хотели.Операторы назначения перемещения
Последним недостающим элементом является оператор присваивания перемещения. Его задача - освободить старый ресурс и получить новый ресурс из его аргумента:
Обратите внимание, что эта реализация оператора присваивания перемещения дублирует логику как деструктора, так и конструктора перемещения. Вы знакомы с идиомой копирования и обмена? Он также может быть применен для перемещения семантики как идиома перемещения и обмена:
Теперь
source
это переменная типаunique_ptr
, она будет инициализирована конструктором перемещения; то есть аргумент будет перемещен в параметр. Аргумент все еще должен быть rvalue, потому что сам конструктор перемещения имеет ссылочный параметр rvalue. Когда поток управления достигает закрывающей скобкиoperator=
,source
выходит из области видимости, автоматически освобождая старый ресурс.Переезд из lvalues
Иногда мы хотим отойти от lvalues. То есть иногда мы хотим, чтобы компилятор обрабатывал lvalue, как если бы он был rvalue, чтобы он мог вызывать конструктор move, даже если он потенциально может быть небезопасным. Для этой цели C ++ 11 предлагает стандартный шаблон библиотечной функции, который вызывается
std::move
внутри заголовка<utility>
. Это имя немного прискорбно, потому чтоstd::move
просто приводит lvalue к rvalue; он ничего не двигает сам по себе. Это просто позволяет двигаться. Возможно это должно было быть названоstd::cast_to_rvalue
илиstd::enable_move
, но мы застряли с именем к настоящему времени.Вот как вы явно переходите от lvalue:
Обратите внимание, что после третьей строки
a
больше не принадлежит треугольник. Это нормально, потому что, явно написавstd::move(a)
, мы четко заявили о наших намерениях: «Дорогой конструктор, делай все, что хочешьa
, чтобы инициализироватьc
; мне уже все равноa
. Не стесняйся иметь свой путьa
».Xvalues
Обратите внимание, что хотя
std::move(a)
это и является значением, его оценка не создает временный объект. Эта загадка вынудила комитет ввести третью категорию стоимости. То, что может быть связано с ссылкой на rvalue, даже если оно не является rvalue в традиционном смысле, называется xvalue (значение eXpiring). Традиционные значения были переименованы в prvalues (чистые значения).И prvalues, и xvalues являются rvalues. Значения xvalue и lvalue являются glvalues (Обобщенные lvalues). Отношения легче понять с помощью диаграммы:
Обратите внимание, что только значения xval действительно новые; остальное только за счет переименования и группировки.
Выход из функций
До сих пор мы видели движение в локальные переменные и в параметры функции. Но движение также возможно в противоположном направлении. Если функция возвращает значение, некоторый объект на сайте вызова (возможно, локальная переменная или временный, но может быть объект любого типа) инициализируется с выражением после
return
оператора в качестве аргумента конструктора перемещения:Возможно, что удивительно, автоматические объекты (локальные переменные, которые не объявлены как
static
) также могут быть неявно удалены из функций:Почему конструктор перемещения принимает значение lvalue
result
в качестве аргумента? Область действияresult
близится к концу, и она будет уничтожена при разматывании стека. Никто не мог потом жаловаться, чтоresult
как-то изменилось; когда поток управления возвращается у вызывающего,result
больше не существует! По этой причине в C ++ 11 есть специальное правило, которое позволяет автоматически возвращать объекты из функций без необходимости писатьstd::move
. Фактически, вы никогда не должны использоватьstd::move
для перемещения автоматических объектов из функций, так как это запрещает «оптимизацию именованных возвращаемых значений» (NRVO).Обратите внимание, что в обеих фабричных функциях тип возвращаемого значения - это значение, а не ссылка на значение. Rvalue-ссылки по-прежнему являются ссылками, и, как всегда, вы никогда не должны возвращать ссылку на автоматический объект; вызывающая сторона получит висячую ссылку, если вы обманом заставите компилятор принять ваш код, например так:
Переезд в члены
Рано или поздно вы напишите такой код:
По сути, компилятор будет жаловаться, что
parameter
это lvalue. Если вы посмотрите на его тип, вы увидите ссылку rvalue, но ссылка rvalue просто означает «ссылку, связанную с rvalue»; это не значит, что сама ссылка является ценным! Действительно,parameter
это просто обычная переменная с именем. Вы можете использоватьparameter
столько раз, сколько захотите, внутри тела конструктора, и он всегда обозначает один и тот же объект. Неявное движение от него было бы опасно, поэтому язык запрещает это.Решение состоит в том, чтобы вручную включить перемещение:
Можно утверждать, что
parameter
больше не используется после инициализацииmember
. Почему не существует специального правила для тихой вставки,std::move
как с возвращаемыми значениями? Возможно, потому что это будет слишком большой нагрузкой для разработчиков компилятора. Например, что, если тело конструктора было в другом модуле перевода? Напротив, правило возвращаемого значения просто должно проверять таблицы символов, чтобы определить,return
обозначает ли идентификатор после ключевого слова автоматический объект.Вы также можете передать
parameter
по значению. Для типов типа «только для перемещения»unique_ptr
, похоже, еще не существует идиомы. Лично я предпочитаю передавать по значению, так как это вызывает меньше помех в интерфейсе.Специальные функции-члены
C ++ 98 неявно объявляет три специальные функции-члены по требованию, то есть когда они где-то нужны: конструктор копирования, оператор присваивания копии и деструктор.
Rvalue ссылки прошли через несколько версий. Начиная с версии 3.0, C ++ 11 объявляет две дополнительные специальные функции-члены по требованию: конструктор перемещения и оператор присваивания перемещения. Обратите внимание, что ни VC10, ни VC11 пока не соответствуют версии 3.0, поэтому вам придется реализовать их самостоятельно.
Эти две новые специальные функции-члены объявляются неявно, только если ни одна из специальных функций-членов не объявляется вручную. Кроме того, если вы объявляете свой собственный конструктор перемещения или оператор присваивания перемещения, ни конструктор копирования, ни оператор присваивания копии не будут объявлены неявно.
Что эти правила означают на практике?
Обратите внимание, что оператор присваивания копии и оператор присваивания перемещения могут быть объединены в единый унифицированный оператор присваивания, принимая его аргумент по значению:
Таким образом, количество специальных функций-членов для реализации уменьшается с пяти до четырех. Здесь есть компромисс между безопасностью исключений и эффективностью, но я не эксперт в этом вопросе.
Пересылка ссылок ( ранее называемых универсальными ссылками )
Рассмотрим следующий шаблон функции:
Вы можете ожидать
T&&
привязки только к rvalue, потому что на первый взгляд это похоже на ссылку на rvalue. Как выясняется, однако,T&&
также привязывается к lvalues:Если аргумент является значением типа
X
,T
выводится какX
, следовательно,T&&
означаетX&&
. Это то, что можно было ожидать. Но если аргумент является lvalue типаX
, из-за специального правила,T
выводитсяX&
, следовательно,T&&
будет означать что-то вродеX& &&
. Но так как C ++ до сих пор понятия не имеет ссылок на ссылки, типX& &&
будет разрушилась вX&
. Поначалу это может показаться запутанным и бесполезным, но свертывание ссылок важно для идеальной пересылки (что здесь не обсуждается).Если вы хотите ограничить шаблон функции значениями r, вы можете объединить SFINAE с чертами типа:
Осуществление переезда
Теперь, когда вы понимаете сворачивание ссылок, вот как
std::move
это реализовано:Как вы видете,
move
принимает любой вид параметра благодаря ссылке на пересылкуT&&
и возвращает ссылку на значение.std::remove_reference<T>::type
Вызов мета-функция необходима , так как в противном случае, для lvalues типаX
, тип возвращаемого быX& &&
, что бы рухнуть вX&
. Так какt
это всегда lvalue (помните, что именованная ссылка rvalue является lvalue), но мы хотим привязатьt
ссылку rvalue, мы должны явно привестиt
к правильному возвращаемому типу. Вызов функции, которая возвращает ссылку на rvalue, сам по себе является xvalue. Теперь вы знаете, откуда взялись xvalues;)Обратите внимание, что возвращение по ссылке rvalue в этом примере хорошо, потому
t
что не обозначает автоматический объект, но вместо этого объект, который был передан вызывающей стороной.источник
Семантика перемещения основана на ссылках rvalue .
Значение r это временный объект, который будет уничтожен в конце выражения. В текущем C ++ значения r связаны только со
const
ссылками. C ++ 1x допускает не-const
значимые ссылки, записанныеT&&
, которые являются ссылками на объекты-значения.Так как значение r умрет в конце выражения, вы можете украсть его данные . Вместо того, чтобы копировать его в другой объект, вы перемещаете в него данные.
В приведенной выше коде, со старыми компиляторами результат
f()
будет скопирован вx
использованииX
«s конструктора копирования. Если ваш компилятор поддерживает семантику перемещения иX
имеет конструктор перемещения, то он вызывается вместо этого. Поскольку егоrhs
аргумент является r-значением , мы знаем, что он больше не нужен, и можем украсть его значение.Таким образом, значение перемещается из безымянного временного объекта , возвращаемого из
f()
вx
(в то время как данныеx
, инициализированные пустымX
, перемещаются во временные, которые будут уничтожены после назначения).источник
this->swap(std::move(rhs));
потому, что именованные ссылки на rvalue являются lvaluesrhs
это именующее в контекстеX::X(X&& rhs)
. Вам нужно позвонить,std::move(rhs)
чтобы получить значение, но это как бы делает ответ спорным.Предположим, у вас есть функция, которая возвращает существенный объект:
Когда вы пишете такой код:
затем обычный компилятор C ++ создаст временный объект для результата
multiply()
, вызовет конструктор копирования для инициализацииr
, а затем уничтожит временное возвращаемое значение. Семантика перемещения в C ++ 0x позволяет вызывать «конструктор перемещения» для инициализацииr
путем копирования его содержимого, а затем отбрасывать временное значение без необходимости его уничтожения.Это особенно важно, если (как, возможно, в
Matrix
примере выше) копируемый объект выделяет дополнительную память в куче для хранения своего внутреннего представления. Конструктор копирования должен будет либо создать полную копию внутреннего представления, либо использовать семантику подсчета ссылок и копирования при записи. Конструктор перемещения оставил бы кучу памяти в одиночестве и просто скопировал указатель внутриMatrix
объекта.источник
Если вы действительно заинтересованы в хорошем и глубоком объяснении семантики перемещения, я настоятельно рекомендую прочитать оригинальную статью о них, «Предложение добавить поддержку семантики перемещения в язык C ++».
Это очень доступно и легко читается, и это превосходное доказательство преимуществ, которые они предлагают. На веб-сайте WG21 есть и другие, более свежие и актуальные статьи о семантике перемещения , но эта, пожалуй, самая прямолинейная, так как она подходит к вещам с точки зрения верхнего уровня и не вдавается в подробности языка.
источник
Семантика перемещения - это передача ресурсов, а не их копирование когда исходное значение больше никому не нужно.
В C ++ 03 объекты часто копируются, только чтобы быть уничтоженными или присвоенными, прежде чем какой-либо код снова использует это значение. Например, когда вы возвращаете значение по значению из функции - если только RVO не активируется - возвращаемое вами значение копируется в кадр стека вызывающей стороны, а затем выходит из области видимости и уничтожается. Это только один из многих примеров: см. Передачу по значению, когда исходный объект является временным, алгоритмы, подобные
sort
этому, просто переставляют элементы, перераспределение,vector
когда егоcapacity()
превышении его и т. Д.Когда такие пары копирования / уничтожения дороги, это обычно потому, что объекту принадлежит какой-то тяжеловесный ресурс. Например,
vector<string>
может иметь динамически распределенный блок памяти, содержащий массивstring
объектов, каждый из которых имеет свою собственную динамическую память. Копирование такого объекта является дорогостоящим: вы должны выделить новую память для каждого динамически распределяемого блока в источнике и скопировать все значения по всему. Затем вам нужно освободить всю память, которую вы только что скопировали. Однако перемещение большогоvector<string>
означает просто копирование нескольких указателей (которые относятся к динамическому блоку памяти) к месту назначения и обнуление их в источнике.источник
В простых (практических) терминах:
Копирование объекта означает копирование его «статических» членов и вызов
new
оператора для его динамических объектов. Правильно?Тем не менее, чтобы двигаться объекта (я повторяю, с практической точки зрения) подразумевает только копирование указателей динамических объектов, а не создание новых.
Но разве это не опасно? Конечно, вы можете дважды уничтожить динамический объект (ошибка сегментации). Таким образом, чтобы избежать этого, вы должны «аннулировать» указатели источника, чтобы не уничтожить их дважды:
Хорошо, но если я перемещу объект, исходный объект станет бесполезным, нет? Конечно, но в определенных ситуациях это очень полезно. Наиболее очевидным является случай, когда я вызываю функцию с анонимным объектом (временный, объект rvalue, ..., вы можете вызывать его с разными именами):
В этой ситуации анонимный объект создается, затем копируется в параметр функции, а затем удаляется. Итак, здесь лучше перемещать объект, потому что вам не нужен анонимный объект, и вы можете сэкономить время и память.
Это приводит к понятию "rvalue" ссылка. Они существуют в C ++ 11 только для определения, является ли полученный объект анонимным или нет. Я думаю, вы уже знаете, что «lvalue» является присваиваемой сущностью (левая часть
=
оператора), поэтому вам нужна именованная ссылка на объект, чтобы иметь возможность выступать в качестве lvalue. Значение r с точностью до наоборот, объект без именованных ссылок. Из-за этого анонимный объект и rvalue являются синонимами. Так:В этом случае, когда объект типа
A
должен быть «скопирован», компилятор создает ссылку lvalue или ссылку rvalue в зависимости от того, назван переданный объект или нет. Если нет, вызывается ваш конструктор перемещения, и вы знаете, что объект является временным, и вы можете перемещать его динамические объекты вместо их копирования, экономя пространство и память.Важно помнить, что «статические» объекты всегда копируются. Нет способов «переместить» статический объект (объект в стек, а не в кучу). Таким образом, различие «перемещение» / «копия», когда объект не имеет динамических членов (прямо или косвенно), не имеет значения.
Если ваш объект сложный и деструктор имеет другие вторичные эффекты, такие как вызов функции библиотеки, вызов других глобальных функций или что-то еще, возможно, лучше сигнализировать о движении с флагом:
Итак, ваш код короче (вам не нужно делать
nullptr
назначение для каждого динамического члена) и более общий.Другой типичный вопрос: в чем разница между
A&&
иconst A&&
? Конечно, в первом случае вы можете изменить объект, а во втором нет, но практический смысл? Во втором случае вы не можете изменить его, поэтому у вас нет способов сделать объект недействительным (кроме как с помощью изменяемого флага или чего-то подобного), и нет никакого практического различия для конструктора копирования.А что такое идеальная пересылка ? Важно знать, что «ссылка на значение» является ссылкой на именованный объект в «области действия вызывающего». Но в реальной области действия ссылка на rvalue является именем объекта, поэтому она действует как именованный объект. Если вы передаете ссылку на rvalue другой функции, вы передаете именованный объект, поэтому объект не воспринимается как временный объект.
Объект
a
будет скопирован в фактический параметрother_function
. Если вы хотите, чтобы объектa
продолжал обрабатываться как временный объект, вы должны использоватьstd::move
функцию:С помощью этой строки
std::move
приведётa
к rvalue иother_function
получит объект как безымянный объект. Конечно, еслиother_function
нет специфической перегрузки для работы с неназванными объектами, это различие не важно.Это идеальная пересылка? Нет, но мы очень близки. Идеальная пересылка полезна только для работы с шаблонами, с целью сказать: если мне нужно передать объект в другую функцию, мне нужно, чтобы, если я получил именованный объект, объект был передан как именованный объект, а когда нет, Я хочу передать его как безымянный объект:
Это сигнатура прототипной функции, которая использует совершенную пересылку, реализованную в C ++ 11 с помощью
std::forward
. Эта функция использует некоторые правила создания шаблона:Таким образом, если
T
l - значение ссылки наA
( T = A &),a
также ( A & && => A &). ЕслиT
это также ссылка на значениеA
,a
также (A && && => A &&). В обоих случаяхa
это именованный объект в реальной области видимости, ноT
содержит информацию о его «ссылочном типе» с точки зрения области действия вызывающей стороны. Эта информация (T
) передается в качестве параметра шаблона,forward
а 'a' перемещается или нет в соответствии с типомT
.источник
Это похоже на семантику копирования, но вместо того, чтобы дублировать все данные, которые вы получаете, чтобы украсть данные из объекта, из которого «перемещаются».
источник
Вы знаете, что означает семантика копирования? это означает, что у вас есть типы, которые можно копировать, для пользовательских типов, которые вы определяете, это либо покупайте явно написав конструктор копирования и оператор присваивания, либо компилятор генерирует их неявно. Это сделает копию.
Семантика перемещения - это в основном пользовательский тип с конструктором, который принимает ссылку на r-значение (новый тип ссылки с использованием && (да, два амперсанда)), который не является константным, это называется конструктором перемещения, то же самое относится и к оператору присваивания. Так что же делает конструктор перемещения, вместо того, чтобы копировать память из аргумента источника, он «перемещает» память из источника в место назначения.
Когда бы вы хотели это сделать? Например, хорошо std :: vector, например, вы создали временный std :: vector и возвращаете его из функции, скажем:
При возврате функции у вас будут накладные расходы от конструктора копирования, если (и это будет в C ++ 0x) std :: vector имеет конструктор перемещения вместо копирования, он может просто установить его указатели и динамически выделить «перемещение» память на новый экземпляр. Это похоже на семантику передачи права собственности с помощью std :: auto_ptr.
источник
Чтобы проиллюстрировать необходимость семантики перемещения , давайте рассмотрим этот пример без семантики перемещения:
Вот функция, которая принимает объект типа
T
и возвращает объект того же типаT
:Вышеуказанная функция использует вызов по значению что означает , что , когда эта функция называется объект должен быть построен для использования этой функции.
Поскольку функция также возвращает значение , для нового значения создается другой новый объект:
Были созданы два новых объекта, один из которых является временным объектом, который используется только на время выполнения функции.
Когда новый объект создается из возвращаемого значения, вызывается конструктор копирования для копирования содержимого временного объекта в новый объект b. После завершения функции временный объект, используемый в функции, выходит из области видимости и уничтожается.
Теперь давайте рассмотрим, что делает конструктор копирования .
Сначала необходимо инициализировать объект, а затем скопировать все соответствующие данные из старого объекта в новый.
В зависимости от класса, может быть, это контейнер с очень большим количеством данных, тогда это может представлять много времени и использования памяти
С семантикой перемещения теперь можно сделать большую часть этой работы менее неприятной, просто перемещая данные, а не копируя.
Перемещение данных включает в себя повторное связывание данных с новым объектом. И никакой копии не происходит вообще.
Это достигается с помощью
rvalue
ссылки. Ссылка работает довольно много , как ссылки с одним важным отличием: Rvalue ссылка может быть перемещенаrvalue
lvalue
и именующий не может.
От cppreference.com :
источник
Я пишу это, чтобы убедиться, что я правильно понимаю.
Семантика перемещения была создана, чтобы избежать ненужного копирования больших объектов. Бьярн Страуструп в своей книге «Язык программирования C ++» использует два примера, где по умолчанию происходит ненужное копирование: один - замена двух больших объектов и два - возврат большого объекта из метода.
Обмен двух больших объектов обычно включает в себя копирование первого объекта во временный объект, копирование второго объекта в первый объект и копирование временного объекта во второй объект. Для встроенного типа это очень быстро, но для больших объектов эти три копии могут занять много времени. «Назначение перемещения» позволяет программисту переопределить поведение копирования по умолчанию и вместо этого поменять местами ссылки на объекты, что означает, что копирование вообще не выполняется и операция подкачки выполняется намного быстрее. Назначение перемещения может быть вызвано путем вызова метода std :: move ().
Возврат объекта из метода по умолчанию включает в себя создание копии локального объекта и связанных с ним данных в месте, доступном для вызывающей стороны (поскольку локальный объект недоступен для вызывающей стороны и исчезает по завершении метода). Когда возвращается встроенный тип, эта операция выполняется очень быстро, но если возвращается большой объект, это может занять много времени. Конструктор перемещения позволяет программисту переопределить это поведение по умолчанию и вместо этого «повторно» использовать данные кучи, связанные с локальным объектом, указав объекту, возвращаемому вызывающей стороне, для кучи данных, связанных с локальным объектом. Таким образом, копирование не требуется.
В языках, которые не позволяют создавать локальные объекты (то есть объекты в стеке), эти типы проблем не возникают, поскольку все объекты размещаются в куче и всегда доступны по ссылке.
источник
x
иy
, вы не можете просто «ссылки своп на объекты» ; может случиться так, что объекты содержат указатели, которые ссылаются на другие данные, и эти указатели можно поменять местами, но операторы перемещения не обязаны ничего менять. Они могут стереть данные из удаленного объекта, а не сохранить в нем данные dest.swap()
без семантики перемещения. «Назначение перемещения можно вызвать, вызвав метод std :: move ()». - иногда необходимо использоватьstd::move()
- хотя это на самом деле ничего не перемещает - просто позволяет компилятору знать, что аргумент является подвижным, иногдаstd::forward<>()
(с пересылкой ссылок), а в других случаях компилятор знает, что значение может быть перемещено.Вот ответ из книги Бьярна Страуструпа «Язык программирования C ++». Если вы не хотите видеть видео, вы можете увидеть текст ниже:
Посмотрите на этот фрагмент. Возврат из оператора + включает в себя копирование результата из локальной переменной
res
в место, где вызывающий может получить к нему доступ.Мы действительно не хотели копировать; мы просто хотели получить результат из функции. Поэтому нам нужно переместить Вектор, а не копировать его. Мы можем определить конструктор перемещения следующим образом:
&& означает «ссылка на rvalue» и является ссылкой, с которой мы можем связать значение rvalue. «rvalue» 'предназначен для дополнения «lvalue», что примерно означает «что-то, что может появиться в левой части назначения». Таким образом, rvalue означает примерно «значение, которое вы не можете назначить», такое как целое число, возвращаемое вызовом функции, и
res
локальная переменная в operator + () для Vectors.Теперь заявление
return res;
не будет копироваться!источник