Могу ли я переместить атрибуты временных объектов в C ++? - PullRequest
2 голосов
/ 15 января 2020

Я пишу итератор, который зависит от элементов в векторе, и фабрику итераторов, которая порождает упомянутые итераторы. Код концептуально равен следующему:

struct Iter
{
  int i = 0;
  vector<int> vec;

  Iter(const vector<int>& _vec):
    vec(_vec)
  {
    cout << "copied vector of size "<<vec.size()<<" for iterator\n";
  }

  Iter(vector<int>&& _vec):
    vec(move(_vec))
  {
    cout << "moved vector of size "<<vec.size()<<" for iterator\n";
  }

  int& operator*() { return vec[i]; }
  Iter& operator++() { ++i; return *this; }
  bool operator!=(const Iter& _it) const { return false; }
};

struct Factory
{
  vector<int> fac_vec;

  Factory(const vector<int>& _v):
    fac_vec(_v)
  {
    cout << "copied vector of size " << fac_vec.size() << " for factory\n";
  }

  Factory(vector<int>&& _v):
    fac_vec(move(_v))
  {
    cout << "moved vector of size "<<fac_vec.size()<<" for factory\n";
  }

  Iter begin() { return Iter(fac_vec); }
  Iter end() { return Iter({}); }
};
int main(){
  for(const int i: Factory({1,2,3}))
    cout << i << "\n";
  return 0;
}

Теперь, выполнение этого кода дает мне (g ++ 8.3):

moved vector of size 3 for factory   [ initialization of the factory with {1,2,3} - moved ]
copied vector of size 3 for iterator [ initialization of the begin-iterator with fac_vec - copied ]
moved vector of size 0 for iterator  [ initialization of the end-iterator with {} - moved ]

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

Я полагаю, что могу делать то, что хочу, используя std::shared_ptr, но это приводит к накладным расходам как в коде, так и в программе. Я бы предпочел сказать компилятору move fac_vec, когда вызывается последний begin(). Есть ли способ сделать это?

1 Ответ

3 голосов
/ 15 января 2020

Обычно при реализации итераторов вы берете итератор вектора или ссылку на вектор:

struct Iter
{
  int i = 0;
  vector<int>* vec;

  Iter(vector<int>& _vec):
    vec(&_vec)
  {
    cout << "copied vector of size "<<vec->size()<<" for iterator\n";
  }

  int& operator*() { return (*vec)[i]; }
  Iter& operator++() { ++i; return *this; }
  bool operator!=(const Iter& _it) const { return false; }
};

Таким образом, вы никогда не копируете.

Лучше используйте собственный итератор вектора:

struct Iter
{
  vector<int>::iterator it;

  Iter(vector<int>::iterator _it):
    it(_it)
  { }

  int& operator*() { return *it; }
  Iter& operator++() { ++it; return *this; }
  bool operator!=(const Iter& _it) const { return false; }
};

Но тогда вы можете удалить Iter и просто использовать векторные итераторы:

struct Factory
{
  // ...    

  auto begin() { return fac_vec.begin(); }
  auto end() { return fac_vec.end(); }
};

Теперь, если вы действительно хотите содержать значение внутри итератора (не рекомендуется) .

Компилятор не будет перемещаться fac_vec, поскольку это lvalue для вектора. Нет движения здесь. Чтобы переместить вектор, вам нужно значение r на вектор.

Это можно получить, перегрузив функцию для эталонного экземпляра rvalue:

struct Factory
{
  vector<int> fac_vec;

  Factory(const vector<int>& _v):
    fac_vec(_v)
  {
    cout << "copied vector of size " << fac_vec.size() << " for factory\n";
  }

  Factory(vector<int>&& _v):
    fac_vec(move(_v))
  {
    cout << "moved vector of size "<<fac_vec.size()<<" for factory\n";
  }

  // moving  when Factory is temporary
  Iter begin() && { return Iter(std::move(fac_vec)); }
  Iter end() && { return Iter({}); }

  // copying
  Iter begin() const& { return Iter(fac_vec); }
  Iter end() const& { return Iter({}); }
};

Но для l oop вызов не будет версия хода. Почему? Это было бы опасно, так как для этого потребовалось бы несколько раз вызывать перемещение для типа.

Диапазон для примерно эквивалентен следующему:

auto&& range = <range expr>; // forwarding reference to range (rvalue in your case)
auto begin = range.begin(); // range is lvalue, so calls the const&
auto end = range.end(); // range is lvalue, so calls the const&

for (/* ... */) {
    // body
}

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

auto begin = std::move(range).begin(); // range is lvalue but moved so calls the &&
auto end = std::move(range).end(); // range is lvalue but moved so calls the &&

Если вы хотите использовать значение в итераторах, что не рекомендуется, вы не можете использовать диапазон для l oop и должны использовать старый стиль для циклов.

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

...