2012-04-01 2 views
10

Durning the weekend Я пытаюсь обновить свои навыки на C++ и изучить некоторые C++ 11, я наткнулся на следующую проблему: я не могу заставить свой класс контейнера правильно использование шаг конструктора:Передача std :: vector to constructor and move semantics

у меня есть класс строитель, который определяется следующим образом:

class builder 
{ 
    ... 
    container build() const 
    { 
    std::vector<items> items; 

    //... fill up the vector 

    return container(items); //should move the vector right? wrong! 
    //return container(std::move(items)); also doesn't work 
    } 
} 

и классы элемент и контейнер, определяется следующим образом:

class container 
{ 
public: 

    container(std:vector<item> items) 
     : items_(items) // always invokes copy constructor on vector, never move 
    { } 

    container(container&& rhs) 
    { 
     ... 
    } 

    ... 

private: 
    std::vector<item> items_; 

} 

class item 
{ 
public: 
    //move .ctor 
    item(item && rhs); 
    item& operator=(item && rhs); 

    //copy .ctor 
    item(const item& rhs); //this gets called instead of move .ctor 
    item& operator=(const item& rhs); 

    ... 
} 

Теперь мой код s предполагает использование

builder my_builder; 
... 
auto result = my_builder.build(); 

, который вызывает каждый элемент будет первым построен, а затем копируется ...

Как я должен написать следующее classess не копировать элементы? Должен ли я просто вернуться к использованию стандартных указателей?

+0

Что такое 'item2'? – Mankarse

+0

@Mankarse это была опечатка, исправленная – ghord

ответ

21

Ваш код должен быть изменен следующим образом:

container(std:vector<item2> items) // may copy OR move 
: items_(std::move(items)) // always moves 
{} 

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

Также: return container(std::move(items));. Я не упоминал об этом раньше, потому что я ошибочно считал, что все локальные переменные автоматически перемещаются в оператор return, но только возвращаемое значение. (Так это должно реально работать: return items;, потому что конструктор container «s не explicit.)

+1

Исключение из этого общего правила заключается в том, что вы должны проходить по ссылке rvalue (а не по значению), если вы передаете очень большие объекты, которые дорого строить даже при перемещении или когда вы пишущий общий код, который должен быть эффективным независимо от типов передаваемых объектов. – Mankarse

+0

Как насчет случая, когда кто-то вызывает этот конструктор с вектором, который будет использоваться позже? Разве это не приведет к перемещению содержимого вектора к одному в контейнере и тем самым удалит их из исходного вектора? – ghord

+0

@Mankarse: Я не вижу это как исключение из правила, потому что правило уже начинается с «если вы хотите свою собственную копию ...». Если вам не нужна копия, не делайте ее. Если вам нужен один, дорогой или нет, это произойдет. – GManNickG

3

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

/// <summary>Container.</summary> 
class Container { 
private: 
    // Here be data! 
    std::vector<unsigned char> _Bytes; 

public: 
    /// <summary>Default constructor.</summary> 
    Container(){ 
    } 

    /// <summary>Copy constructor.</summary> 
    Container(const Container& Copy){ 
     *this = Copy; 
    } 

    /// <summary>Copy assignment</summary> 
    Container& operator = (const Container& Copy){ 
     // Avoid self assignment 
     if(&Copy == this){ 
      return *this; 
     } 
     // Get copying 
     _Bytes = Copy._Bytes; // Copies _Bytes 
     return *this; 
    } 

    /// <summary>Move constructor</summary> 
    Container(Container&& Move){ 
     // You must do this to pass to move assignment 
     *this = std::move(Move); // <- Important 
    } 

    /// <summary>Move assignment</summary> 
    Container& operator = (Container&& Move){ 
     // Avoid self assignment 
     if(&Move == this){ 
      return *this; 
     } 
     // Get moving 
     std::swap(_Bytes, Move._Bytes); // Moves _Bytes 
     return *this; 
    } 
}; // class Container 

Я всегда против использования аргументов значения, как это:

function(std:vector<item2> items) 

Я всегда использую либо:

function(const std:vector<item2>& items) 
function(std:vector<item2>& items) 
function(std:vector<item2>&& items) 

особенно для больших контейнеров данных и редко:

function(std:vector<item2> items) 

для меньшей да ta, никогда не векторы.

Таким образом, вы контролируете происходящее, и именно поэтому мы делаем C++, чтобы контролировать все.

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

Очевидно, что все зависит от того, что вы делаете.

Я являюсь разработчиком на C++. Далеко от эксперта, особенно в C++ сленге ... но обучение :)

+1

[Эффективный аргумент, проходящий в C++ 11] (http://www.codesynthesis.com/~boris/blog/2012/06/26/efficient-argument-passing-cxx11-part2/) ... хорошее объяснение. – CodeAngry