Войти
ПрограммированиеФорумОбщее

C++, семантика перемещения для возращаемого значения

Страницы: 1 2 3 Следующая »
#0
13:15, 10 апр. 2016

Приветствую, господа!

Не могу разобраться, можно ли и как избежать лишнего копирования возращаемого значения из функции при помощи семантики копирования?

К примеру, есть функция, генерирующая массив координат и возвращающая его в качестве std::vector<Point> results;

std::vector<Point> coordinatesOnLine()
{
  std::vector<Point> results;

  //generate...
  
  return results;
}

И пользуемся:

std::vector<Point> coords = coordinatesOnLine();

Понятно, что можно сделать так:

void  coordinatesOnLine(std::vector<Point>& results)
{
  results.clear();

  //generate...  
  
}

Но хотелось бы так же иметь и ф-ию, прямо возвращающую вектор без лишнего копирования. Это возможно? Или я не туда полез и move semantics не для этого?


#1
13:28, 10 апр. 2016

Zackary
> return results;
Это не вызовет никаких конструкторов вообще, так как RVO (return value optimization).

Zackary
> И пользуемся:
> std::vector<Point> coords = coordinatesOnLine();
Здесь будет вызван конструктор перемещения, и деструктор того, что осталось от возвращённого объекта. Если конструктора перемещения у класса нет (но у std::vector он, очевидно, есть), то будет вызван конструктор копирования.

Но вообще ты мог бы сам пошагово продебажить этот код и посмотреть, куда там отладчик заходит.

#2
13:41, 10 апр. 2016

gammaker
> но у std::vector он, очевидно, есть
Есть, но нужно еще требования для Point соблюсти. noexcept деструктор, операторы перемещения как минимум.

#3
13:56, 10 апр. 2016

gammaker

Спасибо!

> Но вообще ты мог бы сам пошагово продебажить этот код и посмотреть, куда там
> отладчик заходит.

Учту, на будущее, но я дремучий любитель)

MATov
> Есть, но нужно еще требования для Point соблюсти. noexcept деструктор,
> операторы перемещения как минимум.

Спасибо, важнно уточнение.

Я правильно понимаю, что оператор перемещения для Point должен выглядеть так?

Point& Point::operator=(Point&& source)
{
  x = source.x;
  y = source.y;
  return *this;
}

Про noexcept деструктор пошел гуглить.

#4
14:03, 10 апр. 2016

MATov
> Есть, но нужно еще требования для Point соблюсти. noexcept деструктор,
> операторы перемещения как минимум.
Зачем? Там же просто перемещение указателей на массивы. Пофиг, что в этих массивах лежит, оно никак не трогается.

#5
14:21, 10 апр. 2016

Zackary
> Я правильно понимаю, что оператор перемещения для Point должен выглядеть так?
Не вижу смысла писать конструктор перемещения и оператор перемещения для данного класса. Перемещение нужно чтобы экономить время на выделении и освобождении памяти и всё что это влечёт.

#6
14:22, 10 апр. 2016

В С++ есть куча категорий значений. Приблизительно, их можно разделить так:
- lvalue - объект, у которого можно взять адрес. Это переменные, функции, поля других объектов, элементы массива, постинкремент и так далее; главное - если выражение &foo компилируется, значит, foo - это lvalue.
- rvalue - соответственно, то, у чего адрес взять нельзя. Это литерные константы, нессылочные результаты из функций (в том числе конструктора), результаты арифметических и логических операций, преинкремент и другие.
Категория значения играет роль в том случае, если оно передаётся аргументом в функцию. lvalue не может быть перемещено, поэтому, оно может быть передано только как lvalue-ссылка, которая обозначается одним амперсандом после базового типа. rvalue переместить можно, поэтому, если имеется перегрузка с rvalue-ссылкой - два амперсанда - вызовется она, однако, если её нет, но есть const lvalue-ссылка, компилятор воспользуется тем, что есть, и привяжет её к передаваемому значению.
Иллюстрация.
Зачем это сделано? Как правило, rvalue - это временные объекты, которые разрушаются сразу после использования. Для некоторых типов - например, std::vector - копирование объекта может занять много времени, поэтому их снабжают специальными, перемещающими, методами, которые, вместо того, чтобы копировать содержимое данного объекта, забирают его, оставляя прежний объект пустым. Продолжая про std::vector, у него, среди прочих, есть такие конструкторы - std::vector::vector( std::vector const& ) и std::vector::vector( std::vector&& ). Оба принимают уже существующий объект и создают новый, идентичный данному. Первый оставляет переданный ему объект неизменным, так что после него у программиста будет два отдельных вектора, которые он может обрабатывать раздельно. Второй заберёт данные и оставит переданный вектор пустым. Поскольку он вызывается для тех объектов, которые всё равно будут разрушены сразу после вызова конструктора, такое поведение оказывается полезным.

А теперь про то, что хочешь сделать ты. С вероятностью 95% компилятор сделает это сам.

#7
14:28, 10 апр. 2016

MATov
> Есть, но нужно еще требования для Point соблюсти. noexcept деструктор,
> операторы перемещения как минимум.
Зачем?
Zackary
> Я правильно понимаю, что оператор перемещения для Point должен выглядеть так?
Нет, не считая Point&& в аргументах, это обычное копирующее присваивание, которое, к тому же, генерируется компилятором автоматически.
А перемещение, как я уже сказал, может быть выполнено через копирующие методы.

#8
14:28, 10 апр. 2016

MATov
> Есть, но нужно еще требования для Point соблюсти. noexcept деструктор,
> операторы перемещения как минимум.
не нужно

Zackary
Просто возвращай свой vector, как ты написал в первом листинге и не парься.

#9
14:42, 10 апр. 2016

Спасибо, господа!

#10
15:02, 10 апр. 2016

Т.е. можно вполне вместо

Point&& Point::operator+(const Point& other) const
{
  return Point(x + other.x, y + other.y);
}

можно смело писать просто

Point Point::operator+(const Point& other) const
{
  return Point(x + other.x, y + other.y);
}

и компилятор оптимизирует всё сам?

#11
15:07, 10 апр. 2016

Zackary
> Т.е. можно вполне вместо
> Point&& Point::operator+(const Point& other) const
> {
> return Point(x + other.x, y + other.y);
> }
> можно смело писать просто
>
> Point Point::operator+(const Point& other) const
> {
> return Point(x + other.x, y + other.y);
> }
> и компилятор оптимизирует всё сам?

ага. стандартная оптимизация RVO

#12
15:22, 10 апр. 2016

Zackary
Более того, первый вариант делает не то, что ты думаешь. Там создаётся объект, тут же разрушается, а вызывающей стороне передаётся ссылка на остатки. Компилятор, кстати, на это ругнётся варнингом, а варнинги надо читать и исправлять.

#13
15:57, 10 апр. 2016

Delfigamer
Да, всё так, уже проверил как раз. Спасибо.

#14
16:25, 10 апр. 2016

Zackary
> Т.е. можно вполне вместо
> Point&& Point::operator+(const Point& other) const
Так нельзя писать. Это возвращение не объекта, а ссылки на временный объект, которого уже не будет при выходе из функции.

Zackary
> можно смело писать просто
> Point Point::operator+(const Point& other) const
Это и есть единственно правильный вариант. Результат сложения будет неявно приводиться к Point&&.
Правда для класса Point, который не управляет памятью и не содержит членов, которые управляют памятью, это абсолютно бессмысленно. Конструктор перемещения не нужен, потому что он делает то же самое, что и конструктор копирования по умолчанию.

Страницы: 1 2 3 Следующая »
ПрограммированиеФорумОбщее

Тема в архиве.