Чи є випадки, коли повернення посилання на RValue (&&) є корисним?


79

Чи є причина, коли функція повинна повертати посилання RValue ? Техніка, або фокус, або ідіома чи шаблон?

MyClass&& func( ... );

Я усвідомлюю небезпеку повернення посилань загалом, але іноді ми все одно робимо це, чи не так? T& T::operator=(T)це лише один ідіоматичний приклад. Але як щодо T&& func(...)? Чи є загальне місце, де ми могли б отримати від цього? Можливо, інакше, коли хтось пише бібліотеку або код API, порівняно із кодом клієнта?

Відповіді:


60

Є кілька випадків, коли це доречно, але вони відносно рідкісні. Випадок наводиться в одному прикладі, коли ви хочете дозволити клієнту перейти від члена даних. Наприклад:

template <class Iter>
class move_iterator
{
private:
    Iter i_;
public:
    ...
    value_type&& operator*() const {return std::move(*i_);}
    ...
};

5
Прекрасний приклад. Шаблон такий: ви хочете, щоб клієнтський код щось переміщував - дозволяючи йому «красти». Так, звісно.
towi

4
Як правило, переміщений з об'єкта при використанні в std :: lib повинен відповідати всім вимогам, визначеним для будь-якої частини std :: lib, яку він використовує. Типи, визначені std, повинні додатково гарантувати, що їх переміщення зі стану є дійсним. Клієнти можуть викликати будь-яку функцію з цим об’єктом, якщо для його значення для цього виклику функції немає попередніх умов.
Говард Хіннант,

3
Нарешті, у наведеному вище прикладі немає об’єктів, що переміщуються. std :: move не рухається. Це лише прикидає rvalue. Клієнт повинен перейти (чи ні) з цього значення. Цей клієнт отримає доступ до значення переміщення, лише якщо він двічі переназначить посилання move_iterator, не втручаючись в обхід ітератора.
Говард Хіннант,

3
Чи не було б безпечніше використовувати value_typeзамість value_type&&типу повернення?
fredoverflow

1
Так, я так думаю. Однак у цьому випадку я думаю, що додаткова вигода перевищує ризик. move_iterator часто використовується в загальному коді для перетворення алгоритму копіювання в рухомий (наприклад, версія-переміщення vector :: insert). Якщо після цього ви надаєте типу дорогу копію та переходите до загального коду, ви отримаєте додаткову копію безоплатно. Я думаю, наприклад, масив <int, N>. Переміщуючи купу цих елементів у вектор, ви не хочете випадково ввести додаткову копію. Що стосується ризику, const X& x = *iце досить рідко. Думаю, ніколи цього не бачив.
Говард Хіннант,

17

Це випливає з коментаря towi. Ви ніколи не хочете повертати посилання на локальні змінні. Але у вас може бути таке:

vector<N> operator+(const vector<N>& x1, const vector<N>& x2) { vector<N> x3 = x1; x3 += x2; return x3; }
vector<N>&& operator+(const vector<N>& x1, vector<N>&& x2)    { x2 += x1; return std::move(x2); }
vector<N>&& operator+(vector<N>&& x1, const vector<N>& x2)    { x1 += x2; return std::move(x1); }
vector<N>&& operator+(vector<N>&& x1, vector<N>&& x2)         { x1 += x2; return std::move(x1); }

Це повинно запобігти будь-яким копіям (і можливим розподілам) у всіх випадках, крім випадків, коли обидва параметри є значеннями l.


1
Незважаючи на те, що це можливо, це, як правило, нарікають, оскільки цей підхід має свої проблеми, крім збереження тимчасових ресурсів. Див stackoverflow.com/questions/6006527
sellibitze

2
Чи не потрібно для цих зворотних дзвінків використовувати std :: move ()?
wjl

1
@wjl: Гарне запитання, але я не думаю. std :: move працює без використання std :: move. Я думаю, що акторський склад && тут робить свою справу.
Клінтон

1
@Clinton у вашому коді немає акторів, вам потрібно return std::move(x2);тощо. Або ви можете написати акторський склад для типу посилання rvalue, але moveце все одно те, що робить.
MM

1
Код правильний лише в тому випадку, якщо значення, що повертається, або не використовується, або присвоєно об’єкту - але тоді ви також можете повернути за значенням і взяти аргументи за значенням і дозволити копіюванню elision зробити своє.
MM

7

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


5
Я думаю , що під час ранньої конструкції C ++ 0x, був час , коли було висловлено припущення про те , що такі речі , як рухатися-правонаступника та T&& operator+(const T&,T&&)повинен повертати &&. Але цього вже немає в остаточному проекті. Тому я питаю.
towi

1

Ви можете повернутися за посиланням, якщо впевнені, що об’єкт, на який посилається, не вийде за межі обсягу після виходу функції, наприклад, це посилання на глобальний об’єкт або функція-член, що повертає посилання на поля класу тощо.

Це правило повернення посилання однаково для посилань lvalue та rvalue. Різниця полягає в тому, як ви хочете використовувати повернене посилання. Як я бачу, повернення за посиланням rvalue є рідкістю. Якщо у вас є функція:

Type&& func();

Вам не сподобається такий код:

Type&& ref_a = func();

оскільки він фактично визначає ref_a як Type &, оскільки іменоване посилання rvalue є значенням, і фактичний рух тут виконуватися не буде. Це схоже на:

const Type& ref_a = func();

за винятком того, що фактичний ref_a є посиланням, яке не є const lvalue.

І це також не дуже корисно, навіть якщо ви безпосередньо передаєте func () іншій функції, яка приймає аргумент Type &&, оскільки вона все ще є іменованим посиланням всередині цієї функції.

void anotherFunc(Type&& t) {
  // t is a named reference
}
anotherFunc(func());

Відносини func () та anotherFunc () більше схожі на "авторизацію", згідно з якою func () погоджується, що anotherFunc () може взяти у власність (або можна сказати "вкрасти") повернутий об'єкт із func (). Але ця угода дуже вільна. Недоступне посилання lvalue все ще може бути "викрадено" абонентами. Насправді функції рідко визначаються для прийняття аргументів посилання rvalue. Найпоширеніший випадок, що "anotherFunc" - це назва класу, а anotherFunc () - насправді конструктор переміщення.


0

Ще один можливий випадок: коли вам потрібно розпакувати кортеж і передати значення функції.

Це може бути корисно в цьому випадку, якщо ви не впевнені в копіюванні.

Такий приклад:

template<typename ... Args>
class store_args{
    public:
        std::tuple<Args...> args;

        template<typename Functor, size_t ... Indices>
        decltype(auto) apply_helper(Functor &&f, std::integer_sequence<size_t, Indices...>&&){
            return std::move(f(std::forward<Args>(std::get<Indices>(args))...));
        }

        template<typename Functor>
        auto apply(Functor &&f){
            return apply_helper(std::move(f), std::make_index_sequence<sizeof...(Args)>{});
        }
};

досить рідкісний випадок, якщо ви не пишете якусь форму std::bindабо std::threadзаміну.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.