Как предотвратить движение нарезки? - PullRequest
0 голосов
/ 20 февраля 2019

Когда экземпляр производного класса передается как r-значение parent ссылка на ничего не подозревающий метод, последний может юридически изменить содержимое родительского элемента, вызывая несогласованность с любыми дополнительными данными, хранящимися в фактическом объекте.Поэтому класс, предназначенный для расширения, не может полагаться на семантику перемещения по умолчанию.Рассмотрим для тривиального примера:

#include <memory>
#include <utility>
#include <iostream>

struct Resource {
  int x;
  Resource(int x_) : x(x_*x_) { }
};

struct A {
  std::unique_ptr<Resource> ptr;

  A(int x) : ptr{std::make_unique<Resource>(x)} { }
  A(A&& other) = default; // i.e. : ptr(std::move(other.ptr)) { }
  virtual ~A() = default;
  // other elements of the rule of 5 left out for brevity

  virtual int value() {
    return ptr ? ptr->x : 0;
  }
};

struct B : A {
  int cached;

  B(int x) : A(x), cached(A::value()) { }

  int value() override {
    return cached;
  }
  int value_parent() {
    return A::value();
  }
};

int main() {
  B b{5};
  std::cout << "Before: b.value() = " << b.value()
        << " (parent: " << b.value_parent() << ")\n";
  A a = std::move(b);
  std::cout << "After: b.value() = " << b.value()
        << " (parent: " << b.value_parent() << ")\n"; // INCONSISTENT!
}

Чтобы передать передачу ресурса наиболее производному классу, я подумал об использовании виртуальной функции для получения ресурса Move-From в конструкторе Move:

... A {
  A(A&& other) : ptr{std::move(other).yield()} { } /**/
  virtual std::unique_ptr<Resource>&& yield() && {
    return std::move(ptr);
  }

... B {
  virtual std::unique_ptr<Resource>&& yield() && override {
    cached = 0;
    return std::move(*this).A::yield(); /**/
  }

Это делает трюк , но имеет две проблемы:

  • становится излишне многословным довольно быстро из-за того, что C ++ "забывает", что параметр функции r-значениябыло && (см. необходимость std::move в строках, помеченных /**/),
  • не может быть легко обобщено, когда нужно yield 'ed.

Есть ли лучшее / каноническое решение?Может быть, я упускаю что-то действительно очевидное.

1 Ответ

0 голосов
/ 20 февраля 2019

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

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

class Base {      
  virtual void moved_fom() {} // do nothing for base
  // some stuff
  // members of the big 5
  virtual ~Base() = default; 
  Base (Base&& other) {
      // do the move
      other->moved_from();
  }
  // etc      
}; 

Теперь любой производный может правильно реагировать на базовую часть, извлекаемую из-под его ног.

...