Переместить конструктор и переместить оператор присваивания против разрешения копирования - PullRequest
2 голосов
/ 13 мая 2019

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

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

Я пытался расположить следующий файл так, чтобынетривиальная реализация вектороподобного класса, использующего семантику перемещения (на самом деле здесь есть и функция main вместе со свободной функцией, облегчающей печать на экране, ...).На самом деле это не минимальный рабочий пример, но вывод, который он выводит на экран, достаточно читабелен, imho.

Тем не менее, если вы считаете, что лучше уменьшить его, пожалуйста, предложите мне, чтоделать.

В любом случае код следующий:

#include<iostream>
using namespace std;

int counter = 0; // to keep count of the created objects

class X {
  private:
    int id = 0; // hopefully unique identifyier
    int n = 0;
    int * p;
  public:
    // special member functions (ctor, dtor, ...)
    X()           : id(counter++), n(0),   p(NULL)       { cout << "default ctor (id " << id << ")\n"; }
    X(int n)      : id(counter++), n(n),   p(new int[n]) { cout << "param ctor (id " << id << ")\n"; };
    X(const X& x) : id(counter++), n(x.n), p(new int[n]) {
      cout << "copy ctor (id " << id << ") (allocating and copying " << n << " ints)\n";
      for (int i = 0; i < n; ++i) {
        p[i] = x.p[i];
      }
    };
    X(X&& x)      : id(counter++), n(x.n), p(x.p) {
      cout << "move ctor (id " << id << ")\n";
      x.p = NULL;
      x.n = 0;
    };
    X& operator=(const X& x) {
      cout << "copy assignment (";
      if (n < x.size() && n > 0) {
        cout << "deleting, ";
        delete [] p;
        n = 0;
      }
      if (n == 0) {
        cout << "allocating, and ";
        p = new int[n];
      }
      n = x.size();
      cout << "copying " << n << " values)";
      for (int i = 0; i < n; ++i) {
        p[i] = x.p[i];
      }
      cout << endl;
      return *this;
    };
    X& operator=(X&& x) {
      this->n = x.n;
      this->p = x.p;
      x.p = NULL;
      x.n = 0;
      cout << "move assignment (\"moving\" " << this->n << " values)\n";
      return *this;
    };
    ~X() {
      cout << "dtor on id " << id << " (array of size " << n << ": " << *this << ")\n";
      delete [] p;
      n = 0;
    }
    // getters/setters
    int size() const { return n; }

    // operators
    int& operator[](int i) const { return p[i]; };
    X operator+(const X& x2) const {
      cout << "operator+\n";
      int n = min(x2.size(), this->size());
      X t(n);
      for (int i = 0; i < n; ++i) {
        t.p[i] = this->p[i] + x2.p[i];
      }
      return t;
    };

    // friend function to slim down the cout lines
    friend ostream& operator<<(ostream&, const X&);
};

int main() {
    X x0;
  X x1(5);
  X x2(5);
  x1[2] = 3;
  x2[3] = 4;
  cout << "\nx0 = x1 + x2;\n";
  x0 = x1 + x2;
  cout << "\nX x4(x1 + x2);\n";
  X x4(x1 + x2);
  cout << x4 << endl;
  cout << '\n';
}

// function to slim down the cout lines
ostream& operator<<(ostream& os, const X& x) {
  os << '[';
  for (int i = 0; i < x.size() - 1; ++i) {
    os << x.p[i] << ',';
  }
  if (x.size() > 0) {
    os << x.p[x.size() - 1];
  }
  return os << ']';
}

Когда я компилирую и запускаю его с

$ clear && g++ moves.cpp && ./a.out

, вывод будет следующим (#-комментарии добавляются вручную)

default ctor (id 0)
param ctor (id 1)
param ctor (id 2)

x0 = x1 + x2;
operator+
param ctor (id 3)
move assignment ("moving" 5 values)
dtor on id 3 (array of size 0: [])

X x4(x1 + x2);
operator+
param ctor (id 4)
[0,0,3,4,0]

dtor on id 4 (array of size 5: [0,0,3,4,0])
dtor on id 2 (array of size 5: [0,0,0,4,0])
dtor on id 1 (array of size 5: [0,0,3,0,0])
dtor on id 0 (array of size 5: [0,0,3,4,0])

Из первой части вывода, я предполагаю, что я действительно продемонстрировал предполагаемое использование оператора назначения перемещения.Прав ли я в этом отношении?(Судя по следующему выводу, это не так, но я не уверен.)

На этом этапе, если мой вывод, что исключение копирования препятствовало вызову ctor, является правильным, тогда один вопросестественно для меня ( и не только для меня, см. комментарий ОП здесь ):

Разве это не ситуация создания объекта на основе другого временного объекта ( например x4, основанный на результате x1 + x2 в X x4(x1 + x2);), именно тот, для которого семантика перемещения должна быть введена?Если нет, то каков базовый пример, демонстрирующий использование ctor-шага?

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

Вывод

clear && g++ -fno-elide-constructors moves.cpp && ./a.out 

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

default ctor (id 0)
param ctor (id 1)
param ctor (id 2)

x0 = x1 + x2;
operator+
param ctor (id 3)
move ctor (id 4)
dtor on id 3 (array of size 0: [])
move assignment ("moving" 5 values)
dtor on id 4 (array of size 0: [])

X x4(x1 + x2);
operator+
param ctor (id 5)
move ctor (id 6)
dtor on id 5 (array of size 0: [])
move ctor (id 7)
dtor on id 6 (array of size 0: [])
[0,0,3,4,0]

dtor on id 7 (array of size 5: [0,0,3,4,0])
dtor on id 2 (array of size 5: [0,0,0,4,0])
dtor on id 1 (array of size 5: [0,0,3,0,0])
dtor on id 0 (array of size 5: [0,0,3,4,0])
+enrico:CSGuild$ 

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

Почему это так?Полностью ли я неправильно понял смысл семантики перемещения?

1 Ответ

3 голосов
/ 13 мая 2019

Похоже, у вас два вопроса здесь:

  • почему конструктор перемещения не вызывается для X x4(x1 + x2)?
  • почему, когда исключено копирование, конструктор перемещения вызывается дважды?

Первый вопрос

Разве это не та ситуация (X x4 (x1 + x2);) точно та, для которой ход семантика где предполагается вводить?

Ну нет. Чтобы использовать семантику перемещения, вы фактически предлагаете нам создать X в operator+, а затем переместить в результат x4, что является явно неэффективным по сравнению с с копией с элидирующим окончательным результатом (x4) на месте во время operator+.

Второй вопрос

Отключив copy-elision, почему мы видим два вызова конструктора перемещения во время X x4(x1 + x2)? Учтите, что здесь есть три прицела:

  1. область действия operator+, где мы создаем X и возвращаем его;
  2. main область, в которую мы звоним X x4(x1 + x2);
  3. X constructor, где мы строим X из x1 + x2;

Тогда, при отсутствии elision, компилятор будет:

  • перемещение результата с operator+ на mainx1 + x2); и
  • перемещение содержимого x1 + x2 в x4.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...