2013-06-14 2 views
13

Статья MSDN, How to: Write a Move Constuctor, имеет следующую рекомендацию.Внедрение конструктора перемещения путем вызова оператора назначения назначения

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

// Move constructor. 
MemoryBlock(MemoryBlock&& other) 
    : _data(NULL) 
    , _length(0) 
{ 
    *this = std::move(other); 
} 

этот код неэффективен двойными инициализации значения MemoryBlock «s, или компилятор сможет оптимизировать расстояние дополнительные инициализации? Должен ли я всегда писать конструкторы перемещения, вызывая оператор назначения перемещения?

ответ

15

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

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

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

Когда вы (или ваш клиент) поместите свой класс в std::vector, лот ходов может быть сгенерирован по вашему типу. Даже если ваш ход молниеносно при 8 загрузках/магазинах, если вы можете сделать это в два раза быстрее, или даже на 50% быстрее только с 4 или 6 грузами/магазинами, imho, который хорошо потрачен.

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

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

+12

Здесь есть несколько хороших советов, но я думаю, что этот ответ пренебрегает тем, что стоимость дублированного кода больше, чем всего 5 минут, потраченных на его запись второй раз - dup code имеет гораздо более значительные затраты, чем в отношении удобочитаемости, ремонтопригодности и надежности. В частности, когда имеется дублированный код, он предлагает очень распространенный тип ошибок: то есть, когда что-то фиксируется или изменяется в одном экземпляре кода, а не в другом. Будучи укушенным тем больше времени, чем мне было бы в жизни, я бы не дублировал код, если не существует * очень * убедительной доказанной причины. –

+2

Производительность является одной из причин, но не единственной или самой важной. В большинстве случаев, когда я пишу оператор move move and move, это потому, что мой класс имеет уникальное право собственности на ресурс. Часто производительность не является проблемой, поэтому в этих случаях имеет смысл избежать дублирования. – opetroch

+0

@ user779446: Спасибо, что разделили ваши приоритеты. Вот мои: http://howardhinnant.github.io/coding_guidelines.html –

1

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

Однако я предпочел бы использовать зЬй :: вперед вместо станд :: двигаться, потому что это более логично:

*this = std::forward<MemoryBlock>(other); 
+3

'std :: forward' предназначен для использования в шаблонах, когда вы не знаете, можете ли вы перемещать объект или нет. – aschepler

+0

Не является ли «другая» переменная уже rvalue, поэтому нет необходимости в std :: move? –

+3

@MikeFisher Nope, имя rvalue является lvalue – TiMoch

0

Это зависит от того, что делает оператор назначения перемещения.Если вы посмотрите на один в статье вы связаны, вы видите, в части:

// Free the existing resource. 
    delete[] _data; 

Таким образом, в этом контексте, если вы назвали оператор присваивания шага от перемещения конструктора без инициализация _data первого, вы бы в конечном итоге пытается удалить неинициализированный указатель. Таким образом, в этом примере, неэффективный или нет, на самом деле важно, чтобы вы инициализировали значения.

+0

Вам не нужно было бы инициализировать значение дважды, если вы использовали список инициализаторов': _data (other._data) ' –

+0

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

2

My C++ 11 версия MemoryBlock класс.

#include <algorithm> 
#include <vector> 
// #include <stdio.h> 

class MemoryBlock 
{ 
public: 
    explicit MemoryBlock(size_t length) 
    : length_(length), 
     data_(new int[length]) 
    { 
    // printf("allocating %zd\n", length); 
    } 

    ~MemoryBlock() noexcept 
    { 
    delete[] data_; 
    } 

    // copy constructor 
    MemoryBlock(const MemoryBlock& rhs) 
    : MemoryBlock(rhs.length_) // delegating to another ctor 
    { 
    std::copy(rhs.data_, rhs.data_ + length_, data_); 
    } 

    // move constructor 
    MemoryBlock(MemoryBlock&& rhs) noexcept 
    : length_(rhs.length_), 
     data_(rhs.data_) 
    { 
    rhs.length_ = 0; 
    rhs.data_ = nullptr; 
    } 

    // unifying assignment operator. 
    // move assignment is not needed. 
    MemoryBlock& operator=(MemoryBlock rhs) // yes, pass-by-value 
    { 
    swap(rhs); 
    return *this; 
    } 

    size_t Length() const 
    { 
    return length_; 
    } 

    void swap(MemoryBlock& rhs) 
    { 
    std::swap(length_, rhs.length_); 
    std::swap(data_, rhs.data_); 
    } 

private: 
    size_t length_; // note, the prefix underscore is reserved. 
    int* data_; 
}; 

int main() 
{ 
    std::vector<MemoryBlock> v; 
    // v.reserve(10); 
    v.push_back(MemoryBlock(25)); 
    v.push_back(MemoryBlock(75)); 

    v.insert(v.begin() + 1, MemoryBlock(50)); 
} 

При правильном C++ 11 компилятор, MemoryBlock::MemoryBlock(size_t) должен вызываться только 3 раза в тестовой программе.

+1

Вы должны использовать std :: unique_ptr вместо того, чтобы вручную удалять ваш массив, что сделало бы его более C++ 11ish – TiMoch

+0

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

0

Я бы просто исключить инициализации члена и писать,

MemoryBlock(MemoryBlock&& other) 
{ 
    *this = std::move(other); 
} 

Это всегда будет работать, если за исключением броска назначения двигаться, и он обычно не делает!

Преимущества этого стиля:

  1. Вам не нужно беспокоиться о том, будет ли компилятор дважды инициализировать элементы, потому что это может варьироваться в различных средах.
  2. Вы пишете меньше кода.
  3. Вам не нужно обновлять его, даже если вы добавите дополнительных членов в класс в будущем.
  4. Компиляторы могут часто включать назначение перемещения, поэтому накладные расходы на конструктор копирования будут минимальными.

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

Смежные вопросы