Я был удивлен, что это не показывается в моих результатах поиска, я думал, что кто-то бы спрашивал это раньше, учитывая полезность семантики перемещения в C ++ 11:
(Причины Другой чем проблемы совместимости с существующим кодом, то есть.)
Ответ Херба (до того, как он был отредактирован) на самом деле дал хороший пример типа, который не должен быть подвижным: std::mutex
,
Собственный тип мьютекса ОС (например, pthread_mutex_t
на платформах POSIX) не может быть «инвариантом местоположения», то есть адрес объекта является частью его значения. Например, ОС может хранить список указателей на все инициализированные объекты мьютекса. Если std::mutex
содержал собственный мьютексный тип ОС в качестве члена данных, и адрес нативного типа должен оставаться фиксированным (поскольку ОС поддерживает список указателей на свои мьютексы), либо std::mutex
пришлось бы хранить собственный тип мьютекса в куче, чтобы он оставался в том же месте при перемещении между std::mutex
объекты или std::mutex
не должен двигаться. Хранить его в куче невозможно, потому что std::mutex
имеет constexpr
конструктор и должен иметь право на постоянную инициализацию (то есть статическую инициализацию), чтобы глобальный std::mutex
гарантированно создается до начала выполнения программы, поэтому ее конструктор не может использовать new
, Таким образом, единственный вариант остается для std::mutex
быть неподвижным.
То же самое относится и к другим типам, которые содержат то, что требует фиксированного адреса. Если адрес ресурса должен оставаться неизменным, не перемещайте его!
Есть еще один аргумент, чтобы не двигаться std::mutex
а это то, что было бы очень трудно сделать это безопасно, потому что вам нужно знать, что никто не пытается заблокировать мьютекс в момент его перемещения. Поскольку мьютексы являются одним из строительных блоков, которые вы можете использовать для предотвращения гонок данных, было бы прискорбно, если бы они не были защищены от самих гонок! С недвижимостью std::mutex
Вы знаете, что единственное, что каждый может сделать с ним после его создания и до его уничтожения, — это заблокировать и разблокировать его, и эти операции явно гарантируют поточнобезопасность и не приводят к гонкам данных. Этот же аргумент относится к std::atomic<T>
объекты: если бы они не могли быть перемещены атомарно, было бы невозможно безопасно перемещать их, другой поток мог бы пытаться вызвать compare_exchange_strong
на объекте прямо в момент его перемещения. Таким образом, другой случай, когда типы не должны быть подвижными, это когда они являются низкоуровневыми строительными блоками безопасного параллельного кода и должны обеспечивать атомарность всех операций над ними. Если значение объекта может быть перемещено в новый объект в любое время, вам нужно использовать атомарную переменную для защиты каждой атомарной переменной, чтобы вы знали, безопасно ли ее использовать или она была перемещена … и атомарную переменную для защиты эта атомная переменная и так далее …
Я думаю, что я бы обобщил, чтобы сказать, что когда объект является просто частью памяти, а не типом, который действует как держатель значения или абстракции значения, его не имеет смысла перемещать. Основные типы, такие как int
не может двигаться: перемещение их — просто копия. Вы не можете вырвать кишки из int
, вы можете скопировать его значение и затем установить его на ноль, но это все еще int
со значением, это просто байты памяти. Но int
все еще движимое в языковых терминах, потому что копия является допустимой операцией перемещения. Однако для не копируемых типов, если вы не хотите или не можете переместить часть памяти, и вы также не можете скопировать ее значение, тогда она не может быть перемещена. Мьютекс или атомарная переменная — это определенное место в памяти (обработанное специальными свойствами), поэтому не имеет смысла перемещаться, а также не копируется, поэтому оно не может быть перемещено.
Краткий ответ: если тип копируемый, он также должен быть перемещаемым. Однако обратное неверно: некоторые типы, такие как std::unique_ptr
подвижны, но копировать их не имеет смысла; это, естественно, только для перемещения типов.
Чуть более длинный ответ следует …
Есть два основных типа типов (среди других более специализированных, таких как черты):
Типы типа значения, такие как int
или же vector<widget>
, Они представляют собой значения и, естественно, должны быть копируемыми. В C ++ 11, как правило, вы должны думать о перемещении как об оптимизации копии, и поэтому все копируемые типы, естественно, должны быть перемещаемыми … перемещение — это всего лишь эффективный способ сделать копию в часто встречающемся случае, который вы не делаете. Тебе больше не нужен оригинальный объект, и он все равно собирается его уничтожить.
Подобные ссылкам типы, которые существуют в иерархиях наследования, такие как базовые классы и классы с виртуальными или защищенными функциями-членами. Они обычно хранятся с помощью указателя или ссылки, часто base*
или же base&
и так не предоставляют конструкцию копирования, чтобы избежать нарезки; если вы хотите получить другой объект, такой же, как существующий, вы обычно вызываете виртуальную функцию clone
, Они не нуждаются в построении перемещения или назначении по двум причинам: они не копируются, и у них уже есть еще более эффективная естественная операция перемещения — вы просто копируете / перемещаете указатель на объект, а сам объект не делает придется перейти на новое место в памяти вообще.
Большинство типов попадают в одну из этих двух категорий, но есть и другие типы типов, которые также полезны, но встречаются реже. В частности, здесь типы, которые выражают уникальное право собственности на ресурс, такие как std::unique_ptr
, являются, естественно, только перемещаемыми типами, потому что они не имеют значения (не имеет смысла копировать их), но вы используете их напрямую (не всегда по указателю или по ссылке) и поэтому хотите перемещать объекты этого типа вокруг из одного места в другое.
На самом деле, когда я искал, я обнаружил, что некоторые типы в 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
Еще одна причина, которую я нашел — производительность.
Скажем, у вас есть класс «а», который содержит значение.
Вы хотите вывести интерфейс, который позволяет пользователю изменять значение в течение ограниченного времени (для области).
Один из способов добиться этого — вернуть объект ‘scope guard’ из ‘a’, который возвращает значение в своем деструкторе, например, так:
class a
{
int value = 0;
public:
struct change_value_guard
{
friend a;
private:
change_value_guard(a& owner, int value)
: owner{ owner }
{
owner.value = value;
}
change_value_guard(change_value_guard&&) = delete;
change_value_guard(const change_value_guard&) = delete;
public:
~change_value_guard()
{
owner.value = 0;
}
private:
a& owner;
};
change_value_guard changeValue(int newValue)
{
return{ *this, newValue };
}
};
int main()
{
a a;
{
auto guard = a.changeValue(2);
}
}
Если бы я сделал change_value_guard перемещаемым, мне пришлось бы добавить ‘if’ к его деструктору, который бы проверял, был ли удален охранник — это дополнительное if и влияние на производительность.
Да, конечно, он может быть оптимизирован любым здравомыслящим оптимизатором, но все-таки приятно, что язык (для этого требуется C ++ 17, хотя для возможности возврата неподвижного типа требуется гарантированное исключение копирования) не требует от нас заплатить это, если мы не собираемся перемещать охрану в любом случае, кроме как вернуть его из функции создания (принцип dont-pay-for-what-you-dont-use).