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&)?

CppNITR
источник
2
Возможный дубликат Чем полезен tr1 :: reference_wrapper?
anderas
Подсказка: что произойдет, если вы сделаете это x = y; в обоих случаях?
juanchopanza
2
В дополнение к моему дублированному флагу (последний комментарий): см., Например, stackoverflow.com/questions/31270810/… и stackoverflow.com/questions/26766939/…
anderas
2
@anderas, дело не в полезности, а в основном в разнице
CppNITR
@CppNITR Тогда просмотрите вопросы, которые я связал за несколько секунд до вашего комментария. Особенно полезен второй.
anderas

Ответы:

91

Well refсоздает объект соответствующего reference_wrapperтипа для хранения ссылки на объект. Что означает, когда вы подаете заявку:

auto r = ref(x);

Это возвращает a, reference_wrapperа не прямую ссылку на x(т.е. T&). Это reference_wrapper(т.е. r) вместо этого выполняется T&.

A reference_wrapperочень полезен, когда вы хотите имитировать 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. Попробуйте сами !!!!!!
Анкит Ачарья
1
На самом деле, нет. Во-первых, воспользуемся точной формулировкой. Вы показываете reference_wrapper, содержащий ссылку на массив, а не reference_wrapper, который сам содержит «ссылку на более чем один объект» . Оболочка содержит только одну ссылку. Во-вторых, я могу легко получить нативную ссылку на массив - вы уверены, что не просто забыли (круглые скобки) вокруг int a[4]{1, 2, 3, 4}; int (&b)[4] = a;? reference_wrapper здесь не особенный, поскольку native T& действительно работает.
underscore_d
1
@AnkitAcharya Да :-) но если быть точным, эффективный результат в стороне, сам по себе относится только к одному объекту. В любом случае, вы, конечно, правы, что в отличие от обычного рефери, он wrapperможет идти в контейнере. Это удобно, но я думаю, что люди неверно истолковывают его как более продвинутый, чем он есть на самом деле. Если мне нужен массив «ссылок», я обычно пропускаю посредника 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)возвращает файл std::reference_wrapper. Это немного больше, чем обернутый указатель, но он распознается библиотекой как «эй, я должен быть ссылкой! Пожалуйста, превратите меня в одного, как только вы закончите передавать меня».
Квентин
38

Ссылка ( T&или T&&) - это особый элемент в языке C ++. Он позволяет управлять объектом по ссылке и имеет особые варианты использования в языке. Например, вы не можете создать стандартный контейнер для хранения ссылок: vector<T&>он плохо сформирован и генерирует ошибку компиляции.

С std::reference_wrapperдругой стороны, A - это объект C ++, способный хранить ссылку. Таким образом, вы можете использовать его в стандартных контейнерах.

std::ref- это стандартная функция, которая возвращает в качестве std::reference_wrapperаргумента. По той же идее std::crefвозвращается std::reference_wrapperк константной ссылке.

Одно интересное свойство a std::reference_wrapper- это то, что у него есть operator T& () const noexcept;. Это означает, что даже если это настоящий объект , он может быть автоматически преобразован в ссылку, которую он держит. Так:

  • поскольку это копируемый объект, который назначается, его можно использовать в контейнерах или в других случаях, когда ссылки не разрешены
  • благодаря этому его operator T& () const noexcept;можно использовать везде, где вы можете использовать ссылку, потому что он будет автоматически преобразован в нее.
Серж Бальеста
источник
1
проголосовали за в основном из-за упоминания о operator T& ()которых не упоминалось в двух других ответах.
Metablaster 08