Как определить конструктор перемещения? - PullRequest
19 голосов
/ 27 февраля 2012

Я пробую некоторые новые возможности C ++ 11 в Visual Studio 11, начатые с конструктора перемещения.Я написал простой класс «MyClass», содержащий конструктор перемещения:

class MyClass
{
public:
    explicit MyClass( int aiCount ) 
        : mpiSize( new int( aiCount ) ),
          miSize2( aiCount)
    {
    }

    MyClass( MyClass&& rcOther )
        : mpiSize( rcOther.mpiSize )
        , miSize( *rcOther.mpiSize )
    {
       rcOther.mpiSize = 0;
       rcOther.miSize = 0;
    }

    ~MyClass() 
    {
        delete mpiSize;
    }

private:
    int *mpiSize;
    int miSize2;

};

У меня возникли вопросы здесь:

  1. Я предполагал, что компилятор сгенерирует конструктор перемещения для MyClassесли я не реализую один - но это не так?
  2. Корректна ли реализация конструктора перемещения для MyClass?
  3. Есть ли лучший способ реализации конструктора перемещения для MyClass?

Ответы [ 2 ]

26 голосов
/ 27 февраля 2012
  1. MSVC ++ реализовал конструкторы перемещения до выхода окончательной версии стандарта. В версии стандарта, основанной на стандарте MSVC ++, правила генерации конструктора перемещения по умолчанию были смехотворно более строгими, чем в окончательной версии стандарта. Смотрите здесь: Почему этот код пытается вызвать конструктор копирования? (в частности, этот ответ и комментарии к нему) для получения дополнительной информации об этом. Это не было и не будет исправлено в Visual Studio 11, по неизвестной глупой причине , потому что у них были другие приоритеты.

  2. Нет, вам нужно вызвать std::move для членов rcOther, и вы инициализируете членов соответствующими членами из умирающего объекта (вы ошибочно назвали miSize):

    MyClass( MyClass&& rcOther )
        : mpiSize( std::move(rcOther.mpiSize) )
        , miSize2( std::move(rcOther.miSize2) )
    {
       rcOther.mpiSize = 0;
    }
    

    Это не имеет значения для встроенных типов, таких как int и int*, но определенно имеет значение для пользовательских типов.

    • Причина этого в том, что std::move просто возвращает аргумент, приведенный к T&&, rvalue-ссылке, так что правильный конструктор (конструктор перемещения, T(T&&)) вызывается для каждого из суб-объекты. Если вы не используете std::move для членов умирающего объекта, они будут обрабатываться как T&, и вместо конструктора перемещения будет вызываться конструктор копирования ваших подобъектов (T(T&)). Это очень плохо и сводит на нет почти всю цель написания вами конструктора перемещения.


3. Вы делаете некоторые ненужные вещи, например, устанавливаете целое число на 0. Вам нужно только установить указатель на 0, чтобы delete не удалял ресурс нового созданного вами объекта.

Кроме того, если это не дидактическое упражнение, вы можете рассмотреть возможность использования std::unique_ptr вместо управления временем жизни вашего собственного объекта. Таким образом, вам даже не придется писать деструктор для вашего класса. Обратите внимание, что если вы сделаете это, использование std::move для инициализации члена от умирающего члена в конструкторе перемещения будет обязательным.

<ч />
  • Конрад Рудольф в своем ответе уловил тот факт, что ваш класс управляет неавтоматическим ресурсом, но не следует правилу Five Three, Four или Five . См. Его ответ для более подробной информации по этому вопросу.
13 голосов
/ 27 февраля 2012

Почему компилятор не генерирует конструктор перемещения автоматически?

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

Верна ли реализация конструктора перемещения?

Конструктор перемещенияправильно 1 но остальная часть класса - нет, вы нарушаете правило из трех : ваш класс нуждается в соответствующем конструкторе копирования и копированииоператор присваивания.

Есть ли лучший способ реализовать конструктор перемещения?

Лучший способ написать конструктор перемещения выглядит следующим образом:

MyClass(MyClass&& rcOther)
    : mpiSize(std::move(rcOther.mpiSize))
    , miSize2(std::move(rcOther.miSize2))
{
    rcOther.mpiSize = 0;
}

Два комментария:

  • Почему вы не скопировали участников напрямую, а разыменовали rcOther.mpiSize?Хотя это не так, это также не имеет смысла и вводит в заблуждение.
  • Вам не нужно обнулять целое число, и поскольку это не нужно, этого не следует делать: единственная модификация, которую должен использовать ваш конструктор перемещениявыполнение объекта «удалено» означает отказ от владения его ресурсами, чтобы его можно было уничтожить, не вызывая двукратного удаления ресурсов.

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


1 Caveat См. Ответ Сета для лучшего объяснения, в котором упоминается std::move (что, однако, в данном конкретном случае запрещено).

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