Как воспользоваться семантикой Move для повышения производительности в C++11?

После многих испытаний я все еще не понимаю, как правильно использовать семантику перемещения, чтобы не копировать результат операции и просто использовать указатель или std:: move для "обмена" указанными данными. Это будет очень полезно для ускорения более сложных функций, таких как f(g(),h(i(l,m),n(),p(q())). Цель состоит в том, чтобы:

t3={2,4,6}; 
t1={}; // empty

При выполнении кода ниже вывод:

t3={2,4,6};
t1={1,2,3};

Код:

namespace MTensor {

 typedef std::vector<double> Tensor1DType;

 class Tensor1D {
  private:
    //std::shared_ptr<Tensor1DType> data = std::make_shared<Tensor1DType>();
    Tensor1DType * data = new Tensor1DType;
  public:
    Tensor1D() {
  };
  Tensor1D(const Tensor1D& other) {
    for(int i=0;i<other.data->size();i++) {
      data->push_back(other.data->at(i));
    }
  }
  Tensor1D(Tensor1D&& other) : data(std::move(other.data)) {
    other.data = nullptr;
  }
  ~Tensor1D() {
    delete data;
  };
  int size() {
    return data->size();
  };
  void insert(double value) {
    data->push_back(value);
  }
  void insert(const std::initializer_list<double>&  valuesList) {
    for(auto value : valuesList) {
      data->push_back(value);
    }
  }
  double operator() (int i) {
    if(i>data->size()) {
      std::cout << "index must be within vector dimension" << std::endl;
      exit(1);
    }
    return data->at(i);
  }
  Tensor1D& operator=(Tensor1D&& other)  {
    if (this == &other){
      return *this;
    }
    data = other.data;
    other.data = nullptr;
    return *this;
  }
  void printTensor(Tensor1DType info) {
    for(int i=0;i<info.size();i++) {
      std::cout << info.at(i) << "," << std::endl;
    }
  }
  void printTensor() {
    for(int i=0;i<data->size();i++) {
      std::cout << data->at(i) << "," << std::endl;
    }
  }
};
} // end of namespace MTensor

В файле main.cpp:

MTensor::Tensor1D scalarProduct1D(MTensor::Tensor1D t1, double scalar) {
  MTensor::Tensor1D tensor;
    for(int i=0;i<t1.size();++i) {
      tensor.insert(t1(i) * scalar);
    }
  //return std::move(tensor);
  return tensor;
}

int main() {
  MTensor::Tensor1D t1;
  t1.insert({1,2,3});
  std::cout << "t1:" << std::endl;
  t1.printTensor();
  MTensor::Tensor1D t3(scalarProduct1D(t1,2));
  std::cout << "t3:" << std::endl;
  t3.printTensor();
  std::cout << "t1:" << std::endl;
  t1.printTensor();
  return 0;
}

2 ответа

Решение

Ваше использование new это красный флаг, особенно на std::vector,

std::vectorПоддержка движений семантики изначально. Это класс управления памятью. Ручное управление памятью класса управления памятью - БОЛЬШОЙ красный флаг.

Следуйте правилу 0. =default ваш конструктор перемещения, назначение перемещения, конструктор копирования, деструктор и назначение копирования. Удалить * из вектора. Не выделяй это. замещать data-> с data.

Второе, что вы должны сделать, это изменить:

MTensor::Tensor1D scalarProduct1D(MTensor::Tensor1D t1, double scalar) {

В нынешнем виде вы принимаете первый аргумент по значению. Это прекрасно.

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

Для того, чтобы это было эффективно, вы захотите иметь способ изменить тензор на месте.

void set(int i, double v) {
  if(i>data->size()) {
    std::cout << "index must be within vector dimension" << std::endl;
    exit(1);
  }
  data.at(i) = v;
}

что дает нам:

MTensor::Tensor1D scalarProduct1D(MTensor::Tensor1D t1, double scalar) {
  for(int i=0;i<t1.size();++i) {
    ts.set(i, t1(i) * scalar);
  }
  return t1; // implicitly moved
}

Мы сейчас приближаемся.

Последнее, что вам нужно сделать, это:

MTensor::Tensor1D t3(scalarProduct1D(std::move(t1),2));

переместить t1 в scalarProduct1D,

Последняя проблема с вашим кодом заключается в том, что вы используете at и вы проверяете границы. atЦель - проверить границы. Если вы используете at, не проверяйте границы (делайте это с помощью try / catch). Если вы проверяете границы, используйте [],

Конечный результат:

typedef std::vector<double> Tensor1DType;

class Tensor1D {
private:
  //std::shared_ptr<Tensor1DType> data = std::make_shared<Tensor1DType>();
  Tensor1DType data;
public:
  Tensor1D() {};
  Tensor1D(const Tensor1D& other)=default;
  Tensor1D(Tensor1D&& other)=default;
  ~Tensor1D()=default;
  Tensor1D& operator=(Tensor1D&& other)=default; 
  Tensor1D& operator=(Tensor1D const& other)=default; 
  Tensor1D(const std::initializer_list<double>&  valuesList) {
    insert(valuesList);
  }
  int size() const {
    return data.size();
  };
  void insert(double value) {
    data.push_back(value);
  }
  void insert(const std::initializer_list<double>&  valuesList) {
    data.insert( data.end(), valuesList.begin(), valuesList.end() );
  }
  double operator() (int i) const {
    if(i>data.size()) {
      std::cout << "index must be within vector dimension" << std::endl;
      exit(1);
    }
    return data[i];
  }
  void set(int i, double v) {
    if(i>data->size()) {
      std::cout << "index must be within vector dimension" << std::endl;
      exit(1);
    }
    data.at(i) = v;
  }
  static void printTensor(Tensor1DType const& info) {
    for(double e : info) {
      std::cout << e << "," << std::endl;
    }
  }
  void printTensor() const {
    printTensor(data);
  }
};

MTensor::Tensor1D scalarProduct1D(MTensor::Tensor1D t1, double scalar) {
  for(int i=0;i<t1.size();++i) {
    t1.set(i, t1(i) * scalar);
  }
  return t1;
}



int main() {
  MTensor::Tensor1D t1 = {1,2,3};
  std::cout << "t1:" << std::endl;
  t1.printTensor();
  MTensor::Tensor1D t3(scalarProduct1D(std::move(t1),2));
  std::cout << "t3:" << std::endl;
  t3.printTensor();
  std::cout << "t1:" << std::endl;
  t1.printTensor();
  return 0;
}

с несколькими другими мелкими исправлениями (такими как использование range-for, DRY и т. д.).

Вы должны двигаться t1 при звонке scalarProduct1Dиначе вы сделаете копию:

MTensor::Tensor1D t3(scalarProduct1D(std::move(t1),2));

Вам нужно явно использовать std::move так как t1 является выражением lvalue.

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

пример живой wandbox

Другие вопросы по тегам