Когда сделать тип неподвижным в C ++ 11?

Я был удивлен, что это не показывается в моих результатах поиска, я думал, что кто-то бы спрашивал это раньше, учитывая полезность семантики перемещения в C ++ 11:

Когда я должен (или это хорошая идея для меня) сделать класс неподвижным в C ++ 11?

(Причины Другой чем проблемы совместимости с существующим кодом, то есть.)

118

Решение

Ответ Херба (до того, как он был отредактирован) на самом деле дал хороший пример типа, который не должен быть подвижным: 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 все еще движимое в языковых терминах, потому что копия является допустимой операцией перемещения. Однако для не копируемых типов, если вы не хотите или не можете переместить часть памяти, и вы также не можете скопировать ее значение, тогда она не может быть перемещена. Мьютекс или атомарная переменная — это определенное место в памяти (обработанное специальными свойствами), поэтому не имеет смысла перемещаться, а также не копируется, поэтому оно не может быть перемещено.

107

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

Краткий ответ: если тип копируемый, он также должен быть перемещаемым. Однако обратное неверно: некоторые типы, такие как std::unique_ptr подвижны, но копировать их не имеет смысла; это, естественно, только для перемещения типов.

Чуть более длинный ответ следует …

Есть два основных типа типов (среди других более специализированных, таких как черты):

  1. Типы типа значения, такие как int или же vector<widget>, Они представляют собой значения и, естественно, должны быть копируемыми. В C ++ 11, как правило, вы должны думать о перемещении как об оптимизации копии, и поэтому все копируемые типы, естественно, должны быть перемещаемыми … перемещение — это всего лишь эффективный способ сделать копию в часто встречающемся случае, который вы не делаете. Тебе больше не нужен оригинальный объект, и он все равно собирается его уничтожить.

  2. Подобные ссылкам типы, которые существуют в иерархиях наследования, такие как базовые классы и классы с виртуальными или защищенными функциями-членами. Они обычно хранятся с помощью указателя или ссылки, часто base* или же base&и так не предоставляют конструкцию копирования, чтобы избежать нарезки; если вы хотите получить другой объект, такой же, как существующий, вы обычно вызываете виртуальную функцию clone, Они не нуждаются в построении перемещения или назначении по двум причинам: они не копируются, и у них уже есть еще более эффективная естественная операция перемещения — вы просто копируете / перемещаете указатель на объект, а сам объект не делает придется перейти на новое место в памяти вообще.

Большинство типов попадают в одну из этих двух категорий, но есть и другие типы типов, которые также полезны, но встречаются реже. В частности, здесь типы, которые выражают уникальное право собственности на ресурс, такие как std::unique_ptr, являются, естественно, только перемещаемыми типами, потому что они не имеют значения (не имеет смысла копировать их), но вы используете их напрямую (не всегда по указателю или по ссылке) и поэтому хотите перемещать объекты этого типа вокруг из одного места в другое.

56

На самом деле, когда я искал, я обнаружил, что некоторые типы в 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

17

Еще одна причина, которую я нашел — производительность.
Скажем, у вас есть класс «а», который содержит значение.
Вы хотите вывести интерфейс, который позволяет пользователю изменять значение в течение ограниченного времени (для области).

Один из способов добиться этого — вернуть объект ‘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).

0
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector