Почему std :: copyable subume std :: movable? - PullRequest
7 голосов
/ 09 июля 2020

Согласно cppreference , std::copyable определяется следующим образом:

template <class T>
concept copyable =
  std::copy_constructible<T> &&
  std::movable<T> && // <-- !!
  std::assignable_from<T&, T&> &&
  std::assignable_from<T&, const T&> &&
  std::assignable_from<T&, const T>;

Мне интересно, почему копируемый объект также должен быть перемещаемым. Просто подумайте о глобальной переменной, к которой обращаются несколько функций. Хотя имеет смысл копировать эту переменную (например, чтобы сохранить ее состояние перед вызовом другой функции), не имеет смысла и на самом деле было бы очень плохо перемещать ее, поскольку другие функции могут не знать, что эта переменная в настоящее время находится в неопределенном состоянии. . Так почему именно std::copyable включает std::movable?

Ответы [ 2 ]

3 голосов
/ 09 июля 2020

Это происходит из двух фактов. Во-первых, даже если вы не определяете конструктор перемещения + назначение перемещения, вы все равно можете создавать / назначать объект из ссылки на r-значение, если вы определяете функции копирования. Просто взгляните на пример:

#include <utility>

struct foo {
    foo() = default;
    foo(const foo&) = default;
    foo& operator=(const foo&) = default;
};

int main()
{
    foo f;
    foo b = std::move(f);
}

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

Обратите внимание, что, поскольку я объявил конструктор копирования, компилятор НЕ Сгенерировал конструктор перемещения по умолчанию.

1 голос
/ 09 июля 2020

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

Здесь есть сильное неустановленное предположение о том, что на самом деле означает движение, что, вероятно, является источником путаницы. Рассмотрим тип:

class Person {
    std::string name;
public:
    Person(std::string);
    Person(Person const& rhs) : name(rhs.name) { }
    Person& operator=(Person const& rhs) { name = rhs.name; return *this; }
};

Что делает moving a Person? Что ж, rvalue типа Person может связываться с Person const& ... и это будет единственный кандидат ... поэтому перемещение вызовет конструктор копирования. Переезд делает копию! Это тоже не редкость - перемещение не имеет , чтобы быть разрушительным или более эффективным, чем копирование, это просто может быть.

В общем, там четыре разумные категории типов:

  1. Типы, для которых перемещение и копирование делают одно и то же (например, int)
  2. Типы, для которых перемещение может быть оптимизацией копирования, которое потребляет ресурсы (например, string или vector<int>)
  3. Типы, которые можно перемещать, но нельзя копировать (например, unique_ptr<int>)
  4. Типы, которые нельзя ни перемещать, ни копировать (например, mutex )

Там много типов, которые попадают в группу 1. И тип переменной, упомянутой в OP, также должен попадать в группу 1.

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

Таким образом, вы можете рассматривать эти группы как своего рода иерархия. (3) расширяет (4), а (1) и (2) расширяет (3) - вы не можете действительно отличить (1) от (2) синтаксически. Следовательно, копируемые подпадают под подвижные.

...