C ++ Різниця між std :: ref (T) та T &?


92

У мене є кілька питань щодо цієї програми:

#include <iostream>
#include <type_traits>
#include <functional>
using namespace std;
template <typename T> void foo ( T x )
{
    auto r=ref(x);
    cout<<boolalpha;
    cout<<is_same<T&,decltype(r)>::value;
}
int main()
{
    int x=5;
    foo (x);
    return 0;
}

Результат:

false

Я хочу знати, якщо std::refне повертає посилання на об'єкт, то що він робить? В основному, в чому різниця між:

T x;
auto r = ref(x);

і

T x;
T &y = x;

Крім того, я хочу знати, чому існує ця різниця? Навіщо нам це потрібно std::refабо std::reference_wrapperколи у нас є посилання (тобто T&)?


2
Можливий дублікат Як корисний tr1 :: reference_wrapper?
anderas

Підказка: що станеться, якщо ви зробите це x = y; в обох випадках?
juanchopanza

2
На додаток до мого дубліката прапора (останній коментар): Дивіться, наприклад stackoverflow.com/questions/31270810 / ... і stackoverflow.com/questions/26766939 / ...
anderas

2
@anderas справа не в корисності, в основному в різниці
CppNITR

@CppNITR Тоді перегляньте запитання, на які я зв’язав, за кілька секунд до Вашого коментаря. Особливо другий корисний.
anderas

Відповіді:


91

Ну refконструює об'єкт відповідного reference_wrapperтипу для зберігання посилання на об'єкт. Що означає, коли ви подаєте заявку:

auto r = ref(x);

Це повертає a, reference_wrapperа не пряме посилання на x(тобто T&). Це reference_wrapper(тобто r) натомість справедливо T&.

A reference_wrapperдуже корисний, коли ви хочете емулювати a referenceоб'єкта, який можна скопіювати (він одночасно може бути створений для копіювання та призначений для копіювання) ).

У C ++, як тільки ви створюєте посилання (скажімо y) на об'єкт (скажімо x), тоді yі xнадаєте ту саму базову адресу . Крім того, yне може посилатися на будь-який інший об'єкт. Крім того, ви не можете створити масив посилань, тобто такий код викличе помилку:

#include <iostream>
using namespace std;

int main()
{
    int x=5, y=7, z=8;
    int& arr[] {x,y,z};    // error: declaration of 'arr' as array of references
    return 0;
}

Однак це законно:

#include <iostream>
#include <functional>  // for reference_wrapper
using namespace std;

int main()
{
    int x=5, y=7, z=8;
    reference_wrapper<int> arr[] {x,y,z};
    for (auto a: arr)
        cout << a << " ";
    return 0;
}
/* OUTPUT:
5 7 8
*/

Говорячи про вашу проблему cout << is_same<T&,decltype(r)>::value;, рішення:

cout << is_same<T&,decltype(r.get())>::value;  // will yield true

Дозвольте показати програму:

#include <iostream>
#include <type_traits>
#include <functional>
using namespace std;

int main()
{
    cout << boolalpha;
    int x=5, y=7;
    reference_wrapper<int> r=x;   // or auto r = ref(x);
    cout << is_same<int&, decltype(r.get())>::value << "\n";
    cout << (&x==&r.get()) << "\n";
    r=y;
    cout << (&y==&r.get()) << "\n";
    r.get()=70;
    cout << y;
    return 0;
}
/* Ouput:
true
true
true
70
*/

Дивіться тут, ми дізнаємось про три речі:

  1. reference_wrapperОб'єкт (тут r) можна використовувати для створення масиву посилань , які не вдалися з T&.

  2. rнасправді діє як справжнє посилання (подивіться, як r.get()=70змінилося значення y).

  3. rне те саме, що є, T&але r.get()є. Це означає, що rмає місце, T&тобто, як випливає з назви, є обгорткою навколо посилання T& .

Сподіваюся, цієї відповіді більш ніж достатньо, щоб пояснити ваші сумніви.


3
1: Ні, a reference_wrapperможна перепризначити , але він не може "містити посилання на більше ніж один об'єкт". 2/3: Справедливий пункт про те, де .get()це доречно - але несуффіксований r може використовуватися так само, як і T&у випадках, коли rперетворення operatorможна викликати однозначно - тому немає необхідності телефонувати .get()у багатьох випадках, включаючи кілька у вашому коді (що важко прочитати через брак просторів).
underscore_d

@underscore_d reference_wrapperможе містити масив посилань, якщо ви не впевнені, тоді ви можете спробувати самі. Плюс .get()використовується, коли ви хочете змінити вартість об’єкта, reference_wrapperякий утримується, тобто r=70є незаконним, тому вам доведеться використовувати r.get()=70. Спробуйте самі !!!!!!
Ankit Acharya

1
Не зовсім. Спочатку скористаємось точними формулюваннями. Ви показуєте reference_wrapper, що містить посилання на масив, а не reference_wrapper, який сам містить "посилання на більше ніж один об'єкт" . Обгортка містить лише одне посилання. По-друге, я можу отримати власне посилання на масив чудово - ви впевнені, що не просто забули (дужки) навколо int a[4]{1, 2, 3, 4}; int (&b)[4] = a;? reference_wrapper не дуже , так як рідні T& робить роботу.
underscore_d

1
@AnkitAcharya Так :-) але якщо бути точним, якщо не брати до уваги ефективний результат, він сам стосується лише одного об'єкта. У будь-якому випадку, ви, звичайно, маєте рацію, що на відміну від звичайного рефера, wrapperконтейнер може йти в контейнер. Це зручно, але я думаю, що люди неправильно трактують це як більш просунуте, ніж воно є насправді. Якщо я хочу масив "refs", я зазвичай пропускаю посередника vector<Item *>, до чого wrapperзводиться ... і сподіваюся, що пуристи-антиуказки мене не знайдуть. Переконливі варіанти використання для цього різні та складніші.
underscore_d

1
"Оператор reference_wrapper дуже корисний, коли ви хочете емулювати посилання на об'єкт, який можна скопіювати." Але хіба це не перешкоджає меті посилання? Ви маєте на увазі фактичну річ. Ось що таке посилання. Посилання, яке можна скопіювати, здається безглуздим, тому що якщо ви хочете скопіювати його, тоді ви не хочете посилання в першу чергу, вам слід просто скопіювати оригінальний об'єкт. Здається, непотрібний рівень складності для вирішення проблеми, яка не повинна існувати в першу чергу.
stu

53

std::reference_wrapper визнається стандартними засобами для передачі об'єктів за посиланням у контексті передачі значення.

Наприклад, std::bindможна взяти std::ref()щось, передати його за значенням і розпакувати назад у посилання пізніше.

void print(int i) {
    std::cout << i << '\n';
}

int main() {
    int i = 10;

    auto f1 = std::bind(print, i);
    auto f2 = std::bind(print, std::ref(i));

    i = 20;

    f1();
    f2();
}

Цей фрагмент виводить:

10
20

Значення iбуло збережено (взято за значенням) у f1момент, коли воно було ініціалізоване, але f2збережене std::reference_wrapperзначення за значенням, і, отже, поводиться так, як прийнято в int&.


2
@CppNITR точно! Дайте мені хвилинку, щоб зібрати невеличку демонстрацію :)
Квентін,

1
як щодо різниці між T & & ref (T)
CppNITR

3
@CppNITR std::ref(T)повертає a std::reference_wrapper. Це трохи більше, ніж обгорнутий покажчик, але бібліотека визнає його "привіт, я маю бути посиланням! Будь ласка, перетворіть мене назад у одного, коли закінчите передавати мене".
Квентін,

38

Посилання ( T&або T&&) - це спеціальний елемент мови С ++. Це дозволяє маніпулювати об’єктом за допомогою посилання та має особливі випадки використання в мові. Наприклад, ви не можете створити стандартний контейнер для зберігання посилань: vector<T&>неправильно сформований та генерує помилку компіляції.

A, std::reference_wrapperз іншого боку, - це об'єкт C ++, здатний утримувати посилання. Таким чином, ви можете використовувати його у звичайних контейнерах.

std::refє стандартною функцією, яка повертає a std::reference_wrapperдля свого аргументу. У тій же ідеї std::crefповертається std::reference_wrapperдо посилання const.

Однією з цікавих властивостей a std::reference_wrapperє те, що він має an operator T& () const noexcept;. Це означає, що навіть якщо це справжній об'єкт , його можна автоматично перетворити на посилання, яке він містить. Тому:

  • оскільки це об’єкт, який можна призначити для копіювання, його можна використовувати в контейнерах або в інших випадках, коли посилання заборонені
  • завдяки цьому його operator T& () const noexcept;можна використовувати де завгодно, де б ви могли використовувати посилання, оскільки воно буде автоматично перетворено в нього.

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