В каких сценариях я должен ожидать, что явным образом потребуется реализовать конструктор перемещения и переместить оператор назначения?
Учитывая, что класс на самом деле является подвижным, вручную реализуя конструктор перемещения и переместить оператор присваивания для класса, быстро становится утомительным.
Мне было интересно, когда , когда это действительно тяжелая, тяжелая, преждевременная оптимизация?
Например, если у класса есть только тривиальные данные POD или члены, которые сами имеют конструктор перемещения и оператор назначения перемещения, определенные, то я предполагаю, что компилятор либо просто оптимизирует дерьмо из партии (в случае POD) и в противном случае использовать конструктор перемещения элементов и переместить оператор присваивания.
Но это гарантировано? В каких сценариях я должен ожидать явно реализовать конструктор перемещения и переместить оператор присваивания?
EDIT: Как упоминалось ниже Николь Болас в комментарии к его ответу на qaru.site/info/346773/... с помощью Visual Studio 11 Beta (и before) no перемещать конструктор или оператор присваивания оператора всегда автоматически генерируется. Ссылка: http://blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx
Ответы
Ответ 1
Если вы обнаружите, что реализуете, любой из:
- деструктор
- конструктор копирования
- назначение копии
Тогда вы должны спросить себя, нужно ли вам реализовать конструкцию перемещения. Если вы "= по умолчанию" вышесказанное, вы должны спросить себя, должны ли вы также "= по умолчанию" перемещать элементы.
Еще важнее то, что вы должны документировать и тестировать свои предположения, например:
static_assert(std::is_nothrow_default_constructible<A>::value, "");
static_assert(std::is_copy_constructible<A>::value, "");
static_assert(std::is_copy_assignable<A>::value, "");
static_assert(std::is_nothrow_move_constructible<A>::value, "");
static_assert(std::is_nothrow_move_assignable<A>::value, "");
static_assert(std::is_nothrow_destructible<A>::value, "");
Ответ 2
Во-первых, переместите семантику только для классов, содержащих ресурсы любого типа. "Плоские" классы вообще не извлекают из этого пользы.
Затем вы должны построить свои классы из "строительных блоков", например, vector
, unique_ptr
и подобных, которые все касаются детализации ресурсов низкого уровня. Если ваш класс будет выполнен как таковой, вам не придется ничего писать вообще, так как компилятор будет правильно генерировать членов для вас.
Если вам нужно написать деструктор для, скажем, регистрации, генерация движений ctors будет отключена, поэтому вам понадобится T(T&&) = default;
для компиляторов, которые его поддерживают. В противном случае это одно из мест, где нужно было написать такой особый член (ну, если только вы не написали такой "строительный блок" ).
Обратите внимание, что ведение журнала в деструкторе и конструкторе можно сделать более простым способом. Просто наследуйте от специального класса, который регистрируется при построении/уничтожении. Или сделайте его переменной-членом. При этом:
tl; dr Пусть компилятор сгенерирует для вас специальный член. Это также учитывает конструктор и оператор присваивания, а также деструктор. Не пишите их сами.
(Хорошо, возможно, операторы присваивания, если вы можете идентифицировать их как горлышко бутылки и хотите их оптимизировать, или если вам нужна специальная безопасность для исключения или что-то подобное. Хотя "строительные блоки" должны уже предоставить все это.)
Ответ 3
Делайте это каждый раз, когда поведение по умолчанию нежелательно или каждый раз, когда по умолчанию они были удалены, и вы все еще нуждаетесь в них.
По умолчанию поведение компилятора для перемещения - это вызов элемента и базы. Для плоских классов/типов buil-in это похоже на копию.
Обычно проблема возникает с классами, содержащими указатели или значение, представляющее ресурсы (например, дескриптор или определенные индексы и т.д.), где для перемещения требуется скопировать значения в новом месте, но также установить старое место в какое-то "нулевое состояние" значение ", распознаваемое деструктором. Во всех остальных случаях поведение по умолчанию - ОК.
Проблема может возникать и при определении копии (и компилятор удаляет перемещение по умолчанию) или перемещение (а компилятор удаляет копию по умолчанию), и вам нужны они оба. В этих случаях достаточно повторного включения значения по умолчанию.
Ответ 4
В каких сценариях я должен явно явно реализовать конструктор перемещения и переместить оператор присваивания?
В следующих случаях:
-
Когда вы используете Visual Studio 10 или 11. Они реализуют ссылки r-value, но не сгенерируют семантику перемещения. Поэтому, если у вас есть тип, в котором есть члены, которые нуждаются в перемещении или даже содержат подвижный тип (std::unique_ptr
и т.д.), Вы должны сами написать ход.
-
Когда вам могут понадобиться операторы копирования/назначения и/или деструктор. Если ваш класс содержит что-то, что заставило вас вручную написать логику копирования или требуется специальная очистка в деструкторе, вероятность того, что вам понадобится переместить логику тоже. Обратите внимание, что это включает удаление механизмов копирования (Type(const Type &t) = delete;
). Если вы не хотите копировать объект, вам, вероятно, потребуется переместить логику или удалить функции move
.
Как говорили другие, вы должны попытаться сохранить минимальное количество типов, которые нуждаются в явных механизмах перемещения или копирования. Поместите их в классы "leaf", и большинство ваших классов полагаются на генерируемые компилятором функции копирования и перемещения. Если вы не используете VS, где вы не получите их...
Примечание. Хороший трюк с операторами переадресации или копирования должен принимать их по значению:
Type &operator=(Type t) { std::swap(*this, t); return *this; }
Если вы сделаете это, он будет охватывать как перемещение, так и копирование в одной функции. Вам все еще нужны отдельные конструкторы перемещения и копирования, но это уменьшает количество функций, которые вы должны записать до 4.
Недостатком является то, что вы эффективно копируете дважды, если Type
состоит только из базовых типов (первая копия в параметр, вторая в swap
). Конечно, вам нужно реализовать /specialize swap
, но это не сложно.
Ответ 5
Прости. Возможно, я пропустил суть вашего вопроса. Я беру ваш вопрос, чтобы понимать конструкторы копирования.
Еще в 1990-х годах, когда я изучил С++, меня учили всегда писать конструктор копий при разработке класса. В противном случае, возможно, это изменилось с более новыми версиями компилятора, С++ создаст для вас конструктор копий в ситуациях, которые его требуют.
Этот конструктор копии по умолчанию может не всегда работать так, как вы хотите. Это особенно верно, если ваш класс содержит указатели, иначе вы не хотите, чтобы по умолчанию была сделана по умолчанию копия конструктора копии по умолчанию.
Наконец, меня научили тому, что, написав конструктор копирования, вы точно контролируете, как вы хотите, чтобы ваш класс копировал.
Надеюсь, это поможет.