Rvalue-ссылка

Кто-нибудь может внятно «на пальцах» рассказать что такое rvalue-ссылка?
Конструктор перемещения, оператор перемещения — читал, monkey-код написать могу. Но не могу уловить суть.
Вот, например, указатель — понятно, в переменной хранится адрес, где лежат данные.
Ссылка — более-менее понятно, в переменной тоже хранится адрес, но обращаемся к ней как будто там хранятся сами данные. Т.е без разыменования.
А вот что такое T&& — не понимаю. Объясните?

Назовите проблемы, присущие lvalue-ссылкам.
Вот для решения части этих проблем и ввели еще rvalue-ссылки.

Давайте взглянем на пару проблем lvalue-ссылок.

  1. void foo(const std::vector<int> &copy_vec) {
    //Выполняем обработку. Работаем именно с копией.
    //Оригинальный вектор не должен изменяться, он нам еще может понадобиться.
    //Или его можно изменять, потому что он больше не нужен в вызывающем коде?
    //Нужен или нет? Вот в чем вопрос.
    }

  2. struct T
    {
    //...
    T(NonCopyableObject &x_): x(x_) {}//Не копируемый объект должен поступать извне.
    NonCopyableObject x;//Его нельзя будет заиметь себе, он же не копируемый.
    };

Обе проблемы решают rvalue-ссылки.
Rvalue-ссылки — это такие же ссылки как и lvalue.
Так что и относиться к ним нужно как к ссылкам.
Ссылка — это ссылка, не важно rvalue она или lvalue.
Но rvalue-ссылка как бы «говорит», что объект,
на который она ссылается больше не нужен,
можно захапать себе его ресурсы,
вместо того, чтобы копировать.

void foo(const std::vector<int> &copy_vec) {
   //Выполняем обработку. Работаем именно с копией. 
   //Оригинальный вектор не должен изменяться, он нам еще может понадобиться.
}

void foo(std::vector<int> &&copy_vec) {
   //Оригинальный вектор больше не нужен, так что можно его изгадить
}


struct T
{
  //...
 T(NonCopyableObject x_): x(std::move(x_)) {}//Не копируемый - значит уникальный, 
  //почему же тогда его не переместить?
  NonCopyableObject x;//Конечно, если он не перемещаемый... но это уже другая история.
};

Примерчик:

class MyString
{
public:
   //...
   //Копирующий конструктор предполагает именно копирование
   MyString(const MyString &src) 
      : mBegin(nullptr)
      , mEnd(nullptr)
      , mCapacity(nullptr)
   {
      std::size_t srcSize = src.mEnd - src.mBegin;
      mCapacity = new char [srcSize];
      mBegin = mCapacity;
      mEnd = mBegin + srcSize;
      memcpy(mBegin, src.mBegin, srcSize);
   }

   //Перемещающий конструктор знает, 
   //что ресурсы передаваемого объекта можно захапать себе
   //так что делать глубокое копирование не нужно.
   MyString(MyString &&src) noexcept
      : mBegin(src.mBegin)
      , mEnd(src.mEnd)
      , mCapacity(src.mCapacity)
   {
      //Но исходный объект теперь тоже содержит те же самые 
      //указатели и при удалении почистит всё как и положено,
     //оставив данный объект с висячими указателями.
     //Так что почистим исходный объект, 
     //но оставив его в валидном состоянии.
      src.mBegin = src.mEnd = src.mCapacity = nullptr;
   }
private:
   char *mBegin;
   char *mEnd;
   char *mCapacity;
};

С оператором присваивания тоже самое,
только еще текущий объект нужно будет почистить.

Итого, rvalue-ссылки служат для того,
чтобы показать, что объект больше не нужен.

Спасибо. Кое-что стало понятнее. Но

Но rvalue-ссылка как бы «говорит», что объект, на который она ссылается больше не нужен,

вот это как-то мутненько. Как компилятор решает когда применить оператор присваивания, а когда оператор перемещения? То же и с конструкторами копии и перемещения.

То же и с конструкторами копии и перемещения.

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

Например:

#include <iostream>


struct X{};


using SomeType = X;

void foo(const SomeType &)
{
    std::cout << "foo_copy" << std::endl;
}


void foo(SomeType &&)
{
    std::cout << "foo_move" << std::endl;
}


int main()
{
    SomeType x;//Именованная переменная - lvalue

    //lvalue не может быть неявно преобразовано к rvalue-reference, 
    //значит здесь однозначно будет выбрана copy-версия foo.
    foo(x);

    //Здесь создается временный объект типа SomeType, 
    //он будет характеризован как rvalue, 
    //будет создана rvalue-ссылка на него,
    //так что будет выбрана версия move foo.
    foo(SomeType());

    //std::move фактически, ничего не перемещает, 
    //просто возвращает rvalue-ссылку на переданный объект. 
    //Будет выбрана версия move foo, ведь мы передает в функцию rvalue-ссылку.
    foo(std::move(x));
}

http://rextester.com/UFD41037

С конструкторами и операторами — тоже самое.
В зависимости от того, что Вы передаете,
будет выбрана та или иная перегрузка.

Спасибо за ответ. Вроде что-то начинает проясняться.

Внимание! Это довольно старый топик, посты в него не попадут в новые, и их никто не увидит. Пишите пост, если хотите просто дополнить топик, а чтобы задать новый вопрос — начните новый.

Ответить

Вы можете использовать разметку markdown для оформления комментариев и постов. Используйте функцию предпросмотра для проверки корректности разметки.

Пожалуйста, оформляйте исходный код в соответствии с правилами разметки. Для того, чтобы вставить код в комментарий, скопируйте его в текстовое поле ниже, после чего выделите то, что скопировали и нажмите кнопку «код» в панели инструментов. Иначе ваш код может принять нечитаемый вид.

Либо производите оформление кода вручную, следующим образом:

``` #include <iostream> using namespace std; int main() { // ... } ```

Предпросмотр сообщения

Ваше сообщение пусто.