В каких сценариях следует ожидать явной необходимости реализовать конструктор перемещения и оператор присваивания перемещения? - PullRequest
16 голосов
/ 01 апреля 2012

Учитывая, что класс на самом деле является подвижным , ручная реализация конструктора перемещения и оператора назначения перемещения для класса быстро становится утомительной.

Мне было интересно , когда это действительно тяжелая, тяжелая, преждевременная оптимизация?

Например, если в классе есть только тривиальные данные POD или члены, для которых сами определены конструктор перемещения и оператор присваивания перемещения, то я предполагаю, что компилятор либо просто оптимизирует дерьмо из партии (в случае POD) и иным образом использовать конструктор перемещения членов и оператор присваивания перемещения.

Но это гарантировано? В каких сценариях я должен ожидать, что явно потребуется реализовать конструктор перемещения и оператор присваивания перемещения?

РЕДАКТИРОВАТЬ: Как упомянуто ниже Николом Боласом в комментарии к его ответу на https://stackoverflow.com/a/9966105/6345, с бета-версией Visual Studio 11 (и до) нет переместить конструктор или переместить оператор присваивания всегда генерируется автоматически. Справка: http://blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx

Ответы [ 5 ]

9 голосов
/ 01 апреля 2012

Если вы обнаружите, что выполняете, любой из:

  • деструктор
  • конструктор копирования
  • копия задания

Тогда вы должны спросить себя, нужно ли вам реализовать конструкцию перемещения. Если вы "= default" по любому из вышеперечисленных, вы должны спросить себя, должны ли вы также "= default" перемещать участников.

Что еще более важно, вы должны документировать и проверять свои предположения, например:

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, "");
8 голосов
/ 01 апреля 2012

Во-первых, семантика перемещения помогает только для классов, которые содержат ресурсы любого вида.«Плоские» классы от этого не выигрывают.

Далее, вы должны строить свои классы из «строительных блоков», таких как vector, unique_ptr и подобных, которые все имеют дело смельчайшие подробности ресурсов.Если ваш класс сделан так, вам не нужно ничего писать, поскольку компилятор будет правильно генерировать элементы для вас.

Если вам нужно написать деструктор, скажем, для ведения журнала, генерацииПеремещение ctors будет отключено, поэтому вам нужно T(T&&) = default; для компиляторов, которые его поддерживают.В противном случае, это единственное место, где можно написать такой специальный элемент самостоятельно (ну, кроме случаев, когда вы пишете такой «строительный блок»).

Обратите внимание, что регистрация в деструкторе и конструкторе может быть выполненаболее простой способ.Просто наследуйте от специального класса, который регистрирует строительство / разрушение.Или сделайте его переменной-членом.При этом:

tl; dr Пусть компилятор сгенерирует для вас специальный элемент.Это также учитывает конструктор копирования и оператор присваивания, а также деструктор.Не пишите их сами.

(Хорошо, может быть, операторы присваивания, если вы можете идентифицировать их как «бутылочную горлышко» и хотите оптимизировать их, или если вам нужна безопасность особых исключений или что-то в этом роде.строительные блоки "уже должны предоставить все это.)

2 голосов
/ 01 апреля 2012

В каких сценариях следует ожидать явной необходимости реализовать конструктор перемещения и оператор присваивания перемещения?

В следующих случаях:

  1. Когда вы используете Visual Studio 10 или 11. Они реализуют ссылки на r-значения, но не генерирует семантику перемещения, сгенерированную компилятором.Поэтому, если у вас есть тип, в котором есть члены, которые нуждаются в перемещении или даже содержат подвижный тип (std::unique_ptr и т. Д.), Вы должны написать ход самостоятельно.

  2. Когда вам может понадобиться скопировать конструкторы / операторы присваивания и / или деструктор.Если ваш класс содержит что-то, что заставило вас писать логику копирования вручную или требует специальной очистки в деструкторе, вероятно, вам понадобится и логика перемещения.Обратите внимание, что это включает удаление механизмов копирования (Type(const Type &t) = delete;).Если вы не хотите копировать объект, то вам, вероятно, понадобится логика перемещения или также удалить функции move.

Как уже говорили другие, вы должны попытаться сохранитьколичество типов, которым необходимо явное перемещение или копирование механизмов до минимума.Поместите их в служебные «листовые» классы, и большинство ваших классов будут полагаться на сгенерированные компилятором функции копирования и перемещения.Если вы не используете VS, где вы не получите эти ...

Примечание: хороший трюк с операторами перемещения или копирования - это взять их по значению:

Type &operator=(Type t) { std::swap(*this, t); return *this; }

Если вы сделаете это, он будет охватывать перемещение и копирование назначения в одной функции.Вам по-прежнему требуется отдельное перемещение и копирование конструкторов , но это сокращает количество функций, которые вы должны написать, до 4.

Недостатком является то, что вы эффективно копируете дважды, если Typeсостоит только из базовых типов (первая копия в параметре, вторая в swap).Конечно, вы должны реализовать / специализовать swap, но это не сложно.

2 голосов
/ 01 апреля 2012

Делайте это каждый раз, когда поведение по умолчанию нежелательно или каждый раз, когда стандартное поведение было удалено, и они вам все еще нужны.

Поведение компилятора по умолчанию для перемещения - это вызов члена и базовое перемещение.Для плоских классов / встроенных типов это похоже на копирование.

Проблема обычно возникает с классами, содержащими указатели или значения, представляющие ресурсы (например, дескриптор, или определенные индексы и т. Д.), Где перемещение требует копирования значенийна новом месте, но также для установки на старом месте некоторого «нулевого значения состояния», распознаваемого деструктором.Во всех других случаях поведение по умолчанию - ОК.

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

0 голосов
/ 01 апреля 2012

Извините. Возможно, я пропустил суть вашего вопроса. Я принимаю ваш вопрос, чтобы иметь в виду конструкторы копирования.

В 1990-х годах, когда я изучал C ++, меня всегда учили писать конструктор копий при разработке класса. В противном случае, и это может измениться в более новых версиях компилятора, C ++ сгенерирует для вас конструктор копирования в ситуациях, когда это требуется.

Этот конструктор копирования по умолчанию может не всегда работать так, как вы хотите. Это было бы особенно верно, если ваш класс содержит указатели, или вы в противном случае не хотите использовать побайтную копию конструктора копирования по умолчанию.

Наконец, меня учили, что, создавая конструктор копирования, вы получаете точный контроль над тем, как вы хотите, чтобы ваш класс копировался.

Надеюсь, это поможет.

...