Я был удивлен, что это не появилось в моих результатах поиска, я подумал, что кто-то спросил бы об этом раньше, учитывая полезность семантики перемещения в C ++ 11:
Когда мне нужно (или это хорошая идея) сделать класс неподвижным в C ++ 11?
(Причины, отличные от проблем совместимости с существующим кодом.)
c++
c++11
move-semantics
c++-faq
user541686
источник
источник
+1
от меня) с очень подробным ответом от Херба (или его близнеца, как кажется ), поэтому я сделал его записью в FAQ. Если кто-то возражает, просто позвоните мне в холле , так что это можно обсудить там.T x = std::move(anotherT);
быть законным» не эквивалентны. Последний является запросом на перемещение, который может обратиться к объекту копирования, если у T нет объекта перемещения. Итак, что именно означает «подвижный»?Ответы:
Ответ Травы (прежде чем он был отредактирован) на самом деле дал хороший пример такого типа , который не должен быть подвижным:
std::mutex
.Собственный тип мьютекса ОС (например,
pthread_mutex_t
на платформах POSIX) может не быть «инвариантным относительно местоположения», что означает, что адрес объекта является частью его значения. Например, ОС может хранить список указателей на все инициализированные объекты мьютекса. Еслиstd::mutex
в качестве члена данных содержится собственный тип мьютекса ОС, а адрес собственного типа должен оставаться фиксированным (поскольку ОС поддерживает список указателей на свои мьютексы), то любой изstd::mutex
них должен будет сохранить собственный тип мьютекса в куче, чтобы он оставался в одно и то же место при перемещении междуstd::mutex
объектами или объектstd::mutex
не должен перемещаться. Сохранение его в куче невозможно, потому что у astd::mutex
естьconstexpr
конструктор и он должен иметь право на постоянную инициализацию (т.е. статическую инициализацию), чтобы глобальныйstd::mutex
гарантированно создается до начала выполнения программы, поэтому его конструктор не может использоватьnew
. Так что остается единственный вариант -std::mutex
оставаться неподвижным.То же самое относится и к другим типам, которые содержат что-то, для чего требуется фиксированный адрес. Если адрес ресурса должен оставаться неизменным, не перемещайте его!
Есть еще один аргумент в пользу отказа от перемещения,
std::mutex
который заключается в том, что было бы очень сложно сделать это безопасно, потому что вам нужно знать, что никто не пытается заблокировать мьютекс в момент его перемещения. Поскольку мьютексы являются одним из строительных блоков, которые вы можете использовать для предотвращения гонок данных, было бы прискорбно, если бы они были небезопасны против самих гонок! С неподвижным объектомstd::mutex
вы знаете, что единственное, что каждый может сделать с ним после того, как он был построен и до того, как он был уничтожен, - это заблокировать и разблокировать его, и эти операции явно гарантируются как потокобезопасные и не вызывают гонок данных. Тот же аргумент применяется кstd::atomic<T>
объектам: если они не могут быть перемещены атомарно, их невозможно будет безопасно перемещать, другой поток может пытаться вызватьcompare_exchange_strong
на объекте прямо в момент его перемещения. Таким образом, другой случай, когда типы не должны быть подвижными, - это когда они являются низкоуровневыми строительными блоками безопасного параллельного кода и должны обеспечивать атомарность всех операций с ними. Если значение объекта может быть перемещено в новый объект в любое время, вам нужно будет использовать атомарную переменную для защиты каждой атомарной переменной, чтобы вы знали, безопасно ли ее использовать или она была перемещена ... и атомарную переменную для защиты эта атомарная переменная и так далее ...Я думаю, что обобщил бы, сказав, что когда объект - это просто чистый фрагмент памяти, а не тип, который действует как держатель для значения или абстракции значения, нет смысла перемещать его. Основные типы, например,
int
не могут двигаться: их перемещение - это просто копия. Вы не можете вырвать кишки из aint
, вы можете скопировать его значение, а затем установить его на ноль, но он все еще имеетint
значение, это просто байты памяти. Ноint
все еще подвиженв терминах языка, потому что копия является допустимой операцией перемещения. Однако для некопируемых типов, если вы не хотите или не можете перемещать фрагмент памяти, а также не можете скопировать его значение, он не может быть перемещен. Мьютекс или атомарная переменная - это определенное место в памяти (обрабатываемое особыми свойствами), поэтому не имеет смысла перемещать, а также не подлежит копированию, поэтому его нельзя перемещать.источник
Краткий ответ: если шрифт копируемый, он также должен быть перемещаемым. Однако обратное неверно: некоторые типы, например,
std::unique_ptr
можно перемещать, но копировать их не имеет смысла; это, естественно, типы только для передвижения.Далее следует чуть более длинный ответ ...
Есть два основных типа типов (среди других, более специализированных, таких как черты характера):
Типы, подобные значениям, например
int
илиvector<widget>
. Они представляют собой значения и, естественно, должны быть копируемыми. В C ++ 11, как правило, вы должны думать о перемещении как об оптимизации копирования, и поэтому все копируемые типы должны, естественно, быть перемещаемыми ... перемещение - это просто эффективный способ создания копии в часто распространенном случае, который вы не делаете. Мне больше не нужен исходный объект, и мы все равно собираемся его уничтожить.Типы, подобные ссылкам, которые существуют в иерархиях наследования, например базовые классы и классы с виртуальными или защищенными функциями-членами. Обычно они хранятся с помощью указателя или ссылки, часто с помощью
base*
илиbase&
, и поэтому не обеспечивают создание копии, чтобы избежать разделения; если вы действительно хотите получить другой объект, такой как существующий, вы обычно вызываете виртуальную функцию, напримерclone
. Они не нуждаются в построении или назначении перемещения по двум причинам: они не копируются, и в них уже есть еще более эффективная естественная операция «перемещения» - вы просто копируете / перемещаете указатель на объект, а сам объект не придется вообще переместиться в новое место в памяти.Большинство типов попадает в одну из этих двух категорий, но есть и другие типы, которые также полезны, но реже. В частности, здесь типы, которые выражают уникальное владение ресурсом, например
std::unique_ptr
, естественно являются типами только для перемещения, потому что они не похожи на значения (нет смысла копировать их), но вы используете их напрямую (не всегда указателем или ссылкой) и поэтому хотите перемещать объекты этого типа из одного места в другое.источник
std::mutex
было неподвижно, поскольку мьютексы POSIX используются по адресу.На самом деле, когда я поискал, я обнаружил, что некоторые типы в C ++ 11 не перемещаются:
mutex
типы (recursive_mutex
,timed_mutex
,recursive_timed_mutex
,condition_variable
type_info
error_category
locale::facet
random_device
seed_seq
ios_base
basic_istream<charT,traits>::sentry
basic_ostream<charT,traits>::sentry
atomic
типыonce_flag
Видимо на Clang идет обсуждение: https://groups.google.com/forum/?fromgroups=#!topic/comp.std.c++/pCO1Qqb3Xa4
источник
iterators / iterator adaptors
следует отредактировать, так как в C ++ 11 есть move_iterator?std::reference_wrapper
. Хорошо, остальные действительно кажутся неподвижными.ios_base
,type_info
,facet
), 3. сортировали странные вещи (sentry
). Вероятно, единственные неподвижные классы, которые напишет средний программист, относятся ко второй категории.Еще одна причина, которую я нашел - производительность. Скажем, у вас есть класс «а», содержащий значение. Вы хотите вывести интерфейс, который позволяет пользователю изменять значение в течение ограниченного времени (для области действия).
Способ добиться этого - вернуть объект «охранник области видимости» из «a», который устанавливает значение обратно в свой деструктор, например:
Если бы я сделал change_value_guard перемещаемым, мне пришлось бы добавить к его деструктору 'if', который проверял бы, был ли перемещен охранник - это дополнительное if и влияние на производительность.
Да, конечно, его, вероятно, можно оптимизировать с помощью любого здравомыслящего оптимизатора, но все же приятно, что язык (для этого требуется C ++ 17, хотя, чтобы иметь возможность возвращать неподвижный тип, требует гарантированного копирования) не требует от нас заплатить, если мы все равно не собираемся перемещать охранник, кроме как вернуть его из функции создания (принцип «не плати за то, что ты не используешь»).
источник