Ответ 1

Забудьте о С++ 0x на данный момент. Семантика перемещения - это то, что не зависит от языка - С++ 0x просто предоставляет стандартный способ выполнения операций с семантикой перемещения.

Определение

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

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

// Copy semantics
assert(b == c);
a = b;
assert(a == b && b == c);

то есть a заканчивается равным b, и мы оставляем b без изменений.

Назначение с семантикой перемещения имеет более слабые почтовые условия:

// Move semantics
assert(b == c);
move(a, b); // not C++0x
assert(a == c);

Обратите внимание, что больше нет гарантии того, что b останется неизменным после назначения с семантикой перемещения. Это принципиальная разница.

Пользы

Одно из преимуществ семантики перемещения заключается в том, что она позволяет выполнять оптимизацию в определенных ситуациях. Рассмотрим следующий тип значения:

struct A { T* x; };

Предположим также, что мы определяем два объекта типа A равными, если их член x указывает на равные значения.

bool operator==(const A& lhs, const A& rhs) { return *lhs.x == *rhs.x; }

Наконец, предположим, что мы определили объект A который будет единоличным владельцем пуантинга их элемента x.

A::~A() { delete x; }
A::A(const A& rhs) : x(new T(rhs.x)) {}
A& A::operator=(const A& rhs) { if (this != &rhs) *x = *rhs.x; }

Теперь предположим, что мы хотим определить функцию для замены двух объектов A

Мы могли бы сделать это обычным способом с семантикой копирования.

void swap(A& a, A& b)
{
    A t = a;
    a = b;
    b = t;
}

Однако это излишне неэффективно. Что мы делаем?

  • Мы создаем копию a в t.
  • Затем мы копируем b в a.
  • Затем скопируйте t в b.
  • Наконец, уничтожить t.

Если объекты T копировать дорого, то это расточительно. Если бы я попросил вас поменять два файла на вашем компьютере, вы бы не создали третий файл, а затем скопировали и вставили содержимое файла перед уничтожением вашего временного файла, не так ли? Нет, вы удалите один файл, переместите второй в первую позицию, а затем, наконец, переместите первый файл обратно во второй. Нет необходимости копировать данные.

В нашем случае легко перемещаться по объектам типа A:

// Not C++0x
void move(A& lhs, A& rhs)
{
    lhs.x = rhs.x;
    rhs.x = nullptr;
}

Мы просто перемещаем указатель rhs в lhs а затем отказываемся от владения rhs этим указателем (устанавливая его в null). Это должно пролить свет на то, почему более слабое состояние после семантики перемещения позволяет оптимизировать.

Определив эту новую операцию перемещения, мы можем определить оптимизированный своп:

void swap(A& a, A& b)
{
    A t;
    move(t, a);
    move(a, b);
    move(b, t);
}

Другое преимущество семантики перемещения заключается в том, что она позволяет перемещаться вокруг объектов, которые невозможно скопировать. Ярким примером этого является std::auto_ptr.

С++ 0x

С++ 0x позволяет перемещать семантику через функцию ссылки на rvalue. В частности, операции такого рода:

a = b;

Имейте семантику перемещения, когда b является ссылкой на значение (пишется T&&), в противном случае они имеют семантику копирования. Вы можете принудительны перемещение семантики с помощью std::move функцию (отличную от move я определен ранее), когда b не является ссылка Rvalue:

a = std::move(b);

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

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

T::T(T&&);
T& operator=(T&&);

Так как эти операции имеют семантику перемещения, вы можете изменять передаваемые аргументы (при условии, что вы оставляете объект в разрушаемом состоянии).

Заключение

Это, по сути, все, что нужно сделать. Обратите внимание, что ссылки на rvalue также используются для обеспечения идеальной пересылки в С++ 0x (из-за специально созданного взаимодействия системы типов между ссылками на rvalue и другими типами), но это на самом деле не связано с семантикой перемещения, поэтому я не обсуждал это здесь.

Ответ 2

В принципе, ссылки rvalue позволяют вам определять, когда объекты являются временными, и вам не нужно сохранять их внутреннее состояние. Это позволяет использовать гораздо более эффективный код, который С++ 03 должен был копировать все время, в С++ 0x вы можете продолжать использовать одни и те же ресурсы. Кроме того, ссылки rvalue обеспечивают идеальную пересылку.

Посмотрите этот ответ.

Ответ 3

Я прочитал тонну текстовых объяснений около года и не понял все о ссылках r-value , пока я не посмотрю эту отличную презентацию Скотта Мейера: http://skillsmatter.com/podcast/home/move-semanticsperfect-forwarding-and-rvalue-references

Он объясняет, что это смешно и достаточно медленно, чтобы понять каждую вещь, происходящую в процессах.

Я знаю, это 1h30, но на самом деле это лучшее объяснение, которое у меня было в прошлом году.

После того, как вы прочитали статьи (как и другие ответы), просмотр этого видео сопел в моем сознании последовательным образом и через несколько дней после того, как я смог объяснить это некоторым коллегам и объяснил, как использовать std:: unique_ptr (поскольку это связано - это только позволяет перемещать семантику, а не копировать), потому что она требует понимания std:: move(), которая требует понимания семантики перемещения.

Ответ 4

рад видеть такой вопрос, и я рад поделиться своей мыслью. Я думаю, вы спрашиваете об исправлении ошибок в определении языка С++, а не только о другой функции языка С++. "Ошибка" существует уже десятки лет. То есть конструктор copy.

Конструкторы копирования кажутся очень странными, если вы знаете, что в физике есть много вещей, которые нельзя копировать как энергию и массу. Это просто шутка, но на самом деле в мире программирования тоже объекты, такие как эксклюзивные дескрипторы файлов, не копируются. Поэтому программисты и дизайнеры С++ придумали некоторые трюки, чтобы справиться с этим. Есть 3 известных: NRVO, boost::noncopyable и std::auto_ptr.

NRVO (Именованная оптимизация возвращаемого значения) - это техническая функция, которая позволяет функции возвращать объект по значению без вызова конструктора копирования. Но проблема с NRVO заключается в том, что хотя конструктор копирования фактически не вызван, требуется конструктор конструктора public, что означает, что объекты boost::noncopyable несовместимы с NRVO.

std::auto_ptr - это еще одно испытание, чтобы обойти конструктор копирования. Возможно, вы видели его "конструктор копирования", реализованный как

template <typename _T>
auto_ptr(auto_ptr<_T>& source)
{
     _ptr = source._ptr; // where _ptr is the pointer to the contained object
     source._ptr = NULL;
}

Это вовсе не копия, а "move" . Вы можете рассматривать такое поведение как прототип семантического перемещения.

Но std::auto_ptr также имеет свою проблему: он несовместим с контейнерами STL. Так что, к сожалению, все, что связано с неготовностью, является болезненным.

Это было болезненно, пока семантика перемещения С++ 0x окончательно не была опубликована и не реализована разработчиками компилятора.

Простым способом можно просто подумать о семантике перемещения как о том же, что и о "копировании" поведения std::auto_ptr, но с полной поддержкой языковых функций, поэтому он отлично работает с контейнерами и алгоритмом.

Кстати, в С++ 0x std::auto_ptr устарел и рекомендуется использовать новый тип шаблона std::unique_ptr.

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