Переместить конструкторы и статические массивы - PullRequest
26 голосов
/ 25 октября 2011

Я изучал возможности Move Constructors в C ++, и мне было интересно, каковы некоторые способы использования этой функции в примере, таком как ниже.Рассмотрим следующий код:

template<unsigned int N>
class Foo {
public:
    Foo() {
        for (int i = 0; i < N; ++i) _nums[i] = 0;
    }

    Foo(const Foo<N>& other) {
        for (int i = 0; i < N; ++i) _nums[i] = other._nums[i];
    }

    Foo(Foo<N>&& other) {
        // ??? How can we take advantage of move constructors here?
    }

    // ... other methods and members

    virtual ~Foo() { /* no action required */ }

private:
    int _nums[N];
};

Foo<5> bar() {
    Foo<5> result;
    // Do stuff with 'result'
    return result;
}

int main() {
    Foo<5> foo(bar());
    // ...
    return 0;
}

В этом примере, приведенном выше, если мы отслеживаем программу (с MSVC ++ 2011), мы видим, что Foo<N>::Foo(Foo<N>&&) вызывается при построении foo, что является желаемым поведением.Однако, если бы у нас не было Foo<N>::Foo(Foo<N>&&), вместо этого был бы вызван Foo<N>::Foo(const Foo<N>&), что сделало бы операцию избыточного копирования.

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

Ответы [ 4 ]

22 голосов
/ 25 октября 2011

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

Таким образом, вопрос сводится к тому, "какой класс компонентов с одной ответственностью может использовать преимущества семантики перемещения?" Общий ответ: все, что управляет ресурсом . Дело в том, что конструктор / присваиватель перемещения просто переустанавливает ресурс для нового объекта и лишает законной силы старый, тем самым избегая (предполагаемого дорогого или невозможного) нового выделения и глубокого копирования ресурса.

Главный пример - это все, что управляет динамической памятью, где операция перемещения просто копирует указатель и устанавливает нулевой указатель старого объекта (поэтому деструктор старого объекта ничего не делает). Вот наивный пример:

class MySpace
{
  void * addr;
  std::size_t len;

public:
  explicit MySpace(std::size_t n) : addr(::operator new(n)), len(n) { }

  ~MySpace() { ::operator delete(addr); }

  MySpace(const MySpace & rhs) : addr(::operator new(rhs.len)), len(rhs.len)
  { /* copy memory */ }

  MySpace(MySpace && rhs) : addr(rhs.addr), len(rhs.len)
  { rhs.len = 0; rhs.addr = 0; }

  // ditto for assignment
};

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

8 голосов
/ 25 октября 2011

В этом случае это бесполезно, потому что int не имеет конструкторов перемещения.

Однако это может быть полезно, если бы вместо этого были строки, например:

template<unsigned int N>
class Foo {
public:
    // [snip]

    Foo(Foo<N>&& other) {
        // move each element from other._nums to _nums
        std::move(std::begin(other._nums), std::end(other._nums), &_nums[0]);
    }

    // [snip]

private:
    std::string _nums[N];
};

ТеперьВы избегаете копировать строки, где будет двигаться.Я не уверен, что соответствующий компилятор C ++ 11 сгенерирует эквивалентный код, если вы полностью опустите все конструкторы копирования / перемещения, извините.

(Другими словами, я не уверен, что std::move специально определено для поэтапного перемещения массивов.)

7 голосов
/ 25 октября 2011

Для написанного вами шаблона класса нет никакого преимущества использовать конструктор перемещения.

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

2 голосов
/ 25 октября 2011

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

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

template<unsigned int N>
class Foo {
public:
    Foo() 
    {
        _nums = new int[N](); //allocate and zeo-initialized
    }
    Foo(const Foo<N>& other) 
    {
        _nums = new int[N];
        for (int i = 0; i < N; ++i) _nums[i] = other._nums[i];
    }

    Foo(Foo<N>&& other) 
    {
         _nums = other._nums; //move the resource
         other._nums=0; //make it null
    }

    Foo<N> operator=(const Foo<N> & other); //implement it!

    virtual ~Foo() { delete [] _nums; }

private:
    int *_nums;
};
...