900 / 477 / 93
Регистрация: 10.06.2014
Сообщений: 2,698
1

Возвращаемый тип как rvalue reference

23.05.2018, 15:35. Показов 5441. Ответов 20
Метки нет (Все метки)

Author24 — интернет-сервис помощи студентам
Нашел интересный пример в книге Мейерса Эффективный и современный С++.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Widget
{
 
public:
    using DataType = std::vector<double>;
    Widget()
    {
        values.push_back(1);
    }
    DataType& data() &
    {
        return values;
    }
    DataType&& data() &&
    { 
        return std::move(values); 
    }
    
private:
    DataType values;
};
Мне интересно, почему в методе DataType&& data() && тип возвращаемого значения указан как rvalue ссылка?
Ведь что бы переместить values в вызываемую сторону этого делать не обязательно.
Достаточно было бы написать так:
C++
1
2
3
4
DataType data() &&
{ 
    return std::move(values); 
}
Добавлено через 4 минуты
П.С: конструктор по умолчанию я добавил сам (в книге его нет)... забыл убрать при публикации
0
Лучшие ответы (1)
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
23.05.2018, 15:35
Ответы с готовыми решениями:

C++ expressions - rvalue, glvalue, prvalue, xvalue, lvalue, а также rvalue reference: что есть что?
Доброго времени суток, не понимаю до конца деление С++ - выражений (приложение 1). Lvalue вроде...

RVALUE Ссылка, error: cannot bind non-const lvalue reference of type 'String&' to an rvalue of type 'String'|
Код не компилируется ниже 17 стандарта с++ с ошибкой error: cannot bind non-const lvalue reference...

Rvalue reference
#include &lt;iostream&gt; std::string get_string() { return std::string(&quot;12345&quot;); } int main()...

Rvalue reference
Добрый вечер! Помогите пожалуйста разобраться с rvalue reference: template &lt;class T&gt; void...

20
Эксперт С++
1624 / 954 / 782
Регистрация: 06.02.2016
Сообщений: 2,452
Записей в блоге: 31
23.05.2018, 16:07 2
del

Добавлено через 25 минут
Мне кажется, что дело в следующем.
values в данном случае станет xvalue
Соответственно в качестве возвращаемого значения мы получим rvalue(xvalue).
И тогда в случае:
C++
1
std::vector<double>v=wt.data();
Оператору присваивания перемещения будет отдаваться предпочтение. (Или же для демонстрации того, что временные лица могут связываться с rvalue-ссылками)
Но это всё лирика
0
techpriest
634 / 213 / 57
Регистрация: 27.02.2014
Сообщений: 1,180
23.05.2018, 16:34 3
Любопытно, что код
C++
1
std::move(wdg).data();
при
C++
1
2
3
4
DataType data() &&
    { 
        return std::move(values); 
    }
Приведет к уничтожению данных, а
C++
1
2
3
4
DataType&& data() &&
    { 
        return std::move(values); 
    }
Не приведет.

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

Добавлено через 2 минуты
И потом ожидается, что метод data таки возвращает ссылку, а не объект.
1
Peoples
23.05.2018, 16:35
  #4

Не по теме:

Mirmik, А (N)RVO было бы только при?

C++
1
2
3
4
DataType data() &&
    { 
        return values; 
    }

0
techpriest
634 / 213 / 57
Регистрация: 27.02.2014
Сообщений: 1,180
23.05.2018, 16:38 5
А зачем задействовать RVO? Это же ничего не даёт.
0
Peoples
23.05.2018, 16:38
  #6

Не по теме:

Mirmik, Точно, объект то не локальный, не временный

0
900 / 477 / 93
Регистрация: 10.06.2014
Сообщений: 2,698
23.05.2018, 17:12  [ТС] 7
Peoples,
RVO/NRVO вроде как работает только для локальных объектов функции...
А в том примере возвращается поле, поэтому думаю компилятор не имеет право применять подобные оптимизации...

Mirmik,
Не очень понятно про неочевидные механизмы.
Согласно примеру из книги, конечная цель это при вызове data()&& переместить вектор в вызывающую сторону, что и происходит даже если не вернуть rvalue ссылку а просто сделать move.

Я думаю что в случае отсутствия при возврате rvalue ссылки, могут возникнуть дополнительные копии (т.е гарантий что сработает только перемещение у нас нет) - это конечно же лишь предположение.
Буду рад если кто нибудь уточнит
0
techpriest
634 / 213 / 57
Регистрация: 27.02.2014
Сообщений: 1,180
23.05.2018, 17:55 8
Если без правой ссылки, возвращен из функции будет временный объект, инициализированный конструктором перемещения, который уже будет присвоен целевому объекту.

Я не знаю наверняка, но компилятор это дело, вероятно оптимизирует и разницы не будет. (большой знак вопроса.)

А если вы вернете правую ссылку, то никаких сложных возвратов не будет. Будет просто присваивание перемещением.
1
900 / 477 / 93
Регистрация: 10.06.2014
Сообщений: 2,698
24.05.2018, 13:43  [ТС] 9
Цитата Сообщение от Mirmik Посмотреть сообщение
Если без правой ссылки, возвращен из функции будет временный объект, инициализированный конструктором перемещения, который уже будет присвоен целевому объекту.
Проверял на трех компиляторах, с правой ссылкой или без нее - результат одинаковый...

Хотелось бы получить более точный ответ, почему так происходит
0
Эксперт С++
1624 / 954 / 782
Регистрация: 06.02.2016
Сообщений: 2,452
Записей в блоге: 31
24.05.2018, 13:44 10
"Если возвращать такую (правую) ссылку, то дело перемещать или копировать, или вообще ничего не делать уже на вызывающей стороне делается." (с) Авторитетный источник
Кликните здесь для просмотра всего текста

std::move ничего не перемещает, как известно
0
900 / 477 / 93
Регистрация: 10.06.2014
Сообщений: 2,698
24.05.2018, 14:08  [ТС] 11
Peoples,
Ну по большому счету в данном случае это же не имеет значения, на какой стороне что делается
Интересен сам результат, который является одинаковым в обоих случаях....

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

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

Это конечно же не утверждение, а лишь мои предположения, фактов у меня нет.
0
techpriest
634 / 213 / 57
Регистрация: 27.02.2014
Сообщений: 1,180
24.05.2018, 14:39 12
...

Ну, это вопрос не о том, что, а о том, как.
Если вам нужно переместить данные в вызывающий объект.
C++
1
std::vector<double> data = getTemporaryWidget().data();
, вы можете пользоваться и тем и другим способом.
Но концептуально вариант с правой ссылкой лучше.

Если вы возвращаете DataType, вы создаете временный объект, перемещаете в него данные и возвращаете его.
Если вы возвращаете DataType&&, вы просто возвращаете ссылку на данные, и вызывающий код уже решает, что с ними делать. Может вообще оставить все как есть и ничего не перемещать.

Добавлено через 2 минуты
Может быть я хочу сделать:

C++
1
size_t how_many_data = getTemporaryWidget().data().size();
Зачем в этом варианте создавать временный объект?
2
900 / 477 / 93
Регистрация: 10.06.2014
Сообщений: 2,698
24.05.2018, 22:04  [ТС] 13
Mirmik,
В принципе меня устраивает ваш ответ, кроме того что вы говорите если возвращаемый тип DataType, то это копия и отсутствие ссылок на стандарт (потому как ситуация не очень то и очевидная). Но в любом случае благодарю за ответ.
0
285 / 176 / 21
Регистрация: 16.02.2018
Сообщений: 666
24.05.2018, 22:50 14
Лучший ответ Сообщение было отмечено Undisputed как решение

Решение

Цитата Сообщение от Undisputed Посмотреть сообщение
Мне интересно, почему в методе DataType&& data() && тип возвращаемого значения указан как rvalue ссылка?
Может, переводчики постарались? Текст под кодом намекает, что в коде не опечатка.
Миниатюры
Возвращаемый тип как rvalue reference  
1
900 / 477 / 93
Регистрация: 10.06.2014
Сообщений: 2,698
24.05.2018, 23:45  [ТС] 15
rat0r,
Спасибо!
Переводить тип возвращаемого значения это конечно сильно
0
techpriest
634 / 213 / 57
Регистрация: 27.02.2014
Сообщений: 1,180
25.05.2018, 11:57 16
Ух ты ж ё...
0
285 / 176 / 21
Регистрация: 16.02.2018
Сообщений: 666
21.05.2019, 17:00 17
Но вообще это Мейерс решил, что лучше бы возвращался DataType&&
Цитата Сообщение от https://www.aristeia.com/BookErrata/emc++-errata.html
A better way to have the rvalue reference overload of the
"data" member function return an rvalue is to have it
return an rvalue reference. That would avoid the creation
of a temporary object for the return value, and it would
be consistent with the by-reference return of the original
"data" interface near the top of page 84.
Я бы с таким не согласился.
0
1659 / 488 / 106
Регистрация: 17.05.2015
Сообщений: 1,497
21.05.2019, 20:34 18
Цитата Сообщение от Undisputed Посмотреть сообщение
Проверял на трех компиляторах, с правой ссылкой или без нее - результат одинаковый...
Как же вы так умыдрились получить одинаковый результат?

https://rextester.com/QFNOQ77094


C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <iostream>
#include <vector>
 
 
struct Value
{
    Value() noexcept { std::cout << "ctor\n"; }
   ~Value()          { std::cout << "dtor\n"; }
    
    Value(const Value&) noexcept { std::cout << "copy\n"; }
    Value(Value&&)      noexcept { std::cout << "move\n"; }
};
 
class Widget
{
    
public:
    using DataType = std::vector<Value>;
    
    ~Widget() 
    {
        std::cout << "Widget: dctor\n";
    }
    
    Widget() : m_values()
    {
        std::cout << "Widget: ctor\n";
        m_values.emplace_back();
    }
    DataType& data() &
    {
        return m_values;
    }
    DataType&& data() &&
    { 
        std::cout << "[1] return DataType&&\n";
        return std::move(m_values); 
    }
   
    DataType values() &&
    { 
        std::cout << "[2] return DataType\n";
        //return std::move(m_values); 
        return m_values; 
    }
    
    DataType values2() &&
    { 
        std::cout << "[2] return move(m_values)\n";
        return std::move(m_values); 
    }
    
    
    
 
    DataType m_values;
};
 
int main()
{
    using DataType = Widget::DataType;
    {
        std::cout << "\nexperiment #1...\n";    
        DataType&& values = Widget().data();
        std::cout << "done!\n";
        (void) values;
    }
    
    {
        std::cout << "\nexperiment #2...\n";    
        DataType&& values = Widget().values();
        std::cout << "done!\n";
        (void) values;
    }
    
    {
        std::cout << "\nexperiment #3...\n";    
        DataType&& values = Widget().values2();
        std::cout << "done!\n";
        (void) values;
    }
    
    {
        std::cout << "\nexperiment #4...\n";    
        DataType&& values = Widget().m_values;
        std::cout << "done!\n";
        (void) values;
    }
    
}
Код
experiment #1...
Widget: ctor
ctor
[1] return DataType&&
Widget: dctor
dtor
done!

experiment #2...
Widget: ctor
ctor
[2] return DataType
copy
Widget: dctor
dtor
done!
dtor

experiment #3...
Widget: ctor
ctor
[2] return move(m_values)
Widget: dctor
done!
dtor

experiment #4...
Widget: ctor
ctor
done!
Widget: dctor
dtor

Наглядно видно, что:
1.
В первом случае получаем невалидную ссылку.
Попытка использовать такую ссылку - ub

2.
Во втором случае выполняется копия объекта.
Не эффективно.

3.
Мембер был перемещен во временный объект.
время жизни временного объекта - до конца работы с итоговой ссылкой.

4.
Итоговая ссылка продлила время жизни всего класса Widget
С одной стороны это наиболее эффективный способ.

С другой - компиляторы до сих пор не осилили эту часть стандарта.
Можно запросто нарваться на протухшую ссылку.

Итого: 3й вариант самый лучший: надежный и эффективный.

(c) hoggy

тёмная сторона с++.
мир тонких граблей, двусмысленных трактовок,
и поведений у различных компиляторов,
ждут отважного путешественника на этой стязе.

известный факт:
константная ссылка на временный объект подливает время его жизни
до конца жизни самой ссылки.

https://ideone.com/mKHcSL
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
 
struct data
{
    ~data() { std::cout << "data: destructor\n"; }
};
 
int main() 
{
    std::cout << "begin...\n";
    {
        const auto& ref = data{};
        std::cout << "work with temporary...\n";
    }
    std::cout << "finished!\n";
}
но что будет, если мы возьмем константную ссылочку не на весь агрегат,
а только на какой то один из мемберов?

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
 
struct data
{
    ~data() { std::cout << "data: destructor\n"; }
};
 
struct sample
{
    data value;
    
    ~sample()   { std::cout << "sample: destructor\n"; }
};
 
int main() 
{
    std::cout << "begin...\n";
    {
        const auto& ref = sample().value;
        std::cout << "work with temporary...\n";
    }
    std::cout << "finished!\n";
}
вывод в консоль:
Код
begin...
work with temporary...
sample: destructor
data: destructor
finished!
продлевается жизнь всего агрегата.
отлично! - скажешь ты, - это логично.
раз время жизни мембера продлевается,
тогда нужно продлить время жизни и всего агрегата,
ведь без хозяина мембер существовать не может.

теперь оставляем в классе хозяине только POD-типы мемберов:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
 
struct data
{
    ~data() { std::cout << "data: destructor\n"; }
};
 
struct sample
{
    //data value;
    int value;
    
    ~sample()   { std::cout << "sample: destructor\n"; }
};
 
int main() 
{
    std::cout << "begin...\n";
    {
        const auto& ref = sample().value;
        std::cout << "work with temporary...\n";
    }
    std::cout << "finished!\n";
}
вывод в консоль:
Код
begin...
sample: destructor
work with temporary...
finished!
и о боже! деструктор агрегата отработал раньше,
чем закончилось время жизни константной ссылки!
в этом случае мы получаем протухшую ссылку.
привет багам!

что примечательно:
на разных компиляторах с разными настройками оптимизации
можно получить разные результаты.

вывод:
ну его нафиг: связываться с "временными мемберами".

практика показывает:
не нужно здесь искать какую то логику,
и на что либо закладываться.
(c) hoggy
сообщение скопировано с другого форума.
0
6770 / 4564 / 1843
Регистрация: 07.05.2019
Сообщений: 13,726
21.05.2019, 21:27 19
Цитата Сообщение от Undisputed Посмотреть сообщение
Мне интересно, почему в методе DataType&& data() && тип возвращаемого значения указан как rvalue ссылка?
Потому что rvalue-ссылка сама по себе ничего не делает. Делает конструктор/оператор переменной которой ты её присвоишь.
C++
1
2
3
Widget w;
std::move(w).data(); //Ничего не поменялось, w.values осталась прежней
auto data = std::move(w).data(); \\values перенесено в дата, если бы у Widget были другие переменные, они б не изменились
0
309 / 221 / 74
Регистрация: 23.05.2011
Сообщений: 981
21.05.2019, 21:27 20
Методы, возвращающие &&-ссылки должны быть только кастами (каким является std::move, например). А код в примере рано или поздно приведёт к UB.

eva2326, пояснил довольно подробно.

Возвращать std::move(x) стоит лишь если x — поле класса, в случае локальной переменной или аргумента сработает (N)RVO или move, т.к. результат функции, возвращённой по значению в любом случае является rvalue с точки зрения вызывающего кода.
0
21.05.2019, 21:27
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
21.05.2019, 21:27
Помогаю со студенческими работами здесь

Rvalue reference and lambda
void foo(A&amp;&amp; a) { auto l = () {}; //a? } Как передать в лямбду rvalue ref как просто...

std::move, rvalue reference
Здравствуйте! Недавно начал разбираться с новыми способами передачи аргументов. Прочитал около 10...

Rvalue reference. Что происходит в коде?
В консоли пусто. Но &quot;worked&quot; выводит. Почему? #include &lt;iostream&gt; using std::cout; class...

что за возвращаемый тип, и как работает функция?
pair&lt;string, string&gt; splitExpression(string expr) { stringstream ss; ss &lt;&lt; expr; pair&lt;string,...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
Опции темы

Новые блоги и статьи
Книги и учебные ресурсы по C#
InfoMaster 08.01.2025
Базовые учебники и руководства Одной из лучших книг для начинающих является "C# 10 и . NET 6 для начинающих" Эндрю Троелсена и Филиппа Джепикса . Книга последовательно раскрывает основные концепции. . .
Что такое NullReferenceEx­­­ception и как исправить?
InfoMaster 08.01.2025
NullReferenceException - одно из самых распространенных исключений, с которым сталкиваются разработчики на C#. Это исключение возникает при попытке обратиться к членам объекта (методам, свойствам или. . .
Что такое Null Pointer Exception (NPE) и как это исправить?
InfoMaster 08.01.2025
Null Pointer Exception (NPE) - это одно из самых распространенных исключений в Java, которое возникает при попытке использовать ссылку на объект, значение которой равно null. Это исключение относится. . .
Русский язык в консоли C++
InfoMaster 08.01.2025
При разработке программ на C++ одной из частых проблем, с которой сталкиваются русскоязычные программисты, является корректное отображение кириллицы в консольных приложениях. Эта проблема особенно. . .
Telegram бот на C#
InfoMaster 08.01.2025
Разработка ботов для Telegram стала неотъемлемой частью современной экосистемы мессенджеров. C# предоставляет мощный и удобный инструментарий для создания разнообразных ботов, от простых. . .
Использование GraphQL в Go (Golang)
InfoMaster 08.01.2025
Go (Golang) является одним из наиболее популярных языков программирования, используемых для создания высокопроизводительных серверных приложений. Его архитектурные особенности и встроенные. . .
Что лучше использовать при создании класса в Java: сеттеры или конструктор?
Alexander-7 08.01.2025
Вопрос подробнее: На вопрос: «Когда одновременно создаются конструктор и сеттеры в классе – это нормально?» куратор уточнил: «Ваш класс может вообще не иметь сеттеров, а только конструктор и геттеры. . .
Как работать с GraphQL на TypeScript
InfoMaster 08.01.2025
Введение в GraphQL и TypeScript В современной разработке веб-приложений GraphQL стал мощным инструментом для создания гибких и эффективных API. В сочетании с TypeScript, эта технология. . .
Счётчик на базе сумматоров + регистров и генератора сигналов согласования.
Hrethgir 07.01.2025
Создан с целью проверки скорости асинхронной логики: ранее описанного сумматора и предополагаемых fast регистров. Регистры созданы на базе ранее описанного, предполагаемого fast триггера. То-есть. . .
Как перейти с Options API на Composition API в Vue.js
BasicMan 06.01.2025
Почему переход на Composition API актуален В мире современной веб-разработки фреймворк Vue. js продолжает эволюционировать, предлагая разработчикам все более совершенные инструменты для создания. . .
Архитектура современных процессоров
inter-admin 06.01.2025
Процессор (центральный процессор, ЦП) является основным вычислительным устройством компьютера, которое выполняет обработку данных и управляет работой всех остальных компонентов системы. Архитектура. . .
История создания реляционной модели баз данных, правила Кодда
Programming 06.01.2025
Предпосылки создания реляционной модели В конце 1960-х годов компьютерная индустрия столкнулась с серьезными проблемами в области управления данными. Существовавшие на тот момент модели данных -. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru