C++ Разница между std::ref(T) и T&?

У меня есть вопросы по этой программе:

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


person CppNITR    schedule 20.10.2015    source источник
comment
Возможный дубликат Чем полезен tr1::reference_wrapper?   -  person anderas    schedule 20.10.2015
comment
Подсказка: что произойдет, если вы сделаете x = y; в обоих случаях?   -  person juanchopanza    schedule 20.10.2015
comment
В дополнение к моему повторяющемуся флагу (последний комментарий): см., например, stackoverflow.com/questions/31270810/ и stackoverflow.com/questions/26766939/   -  person anderas    schedule 20.10.2015
comment
@anderas дело не в полезности, а в основном в разнице   -  person CppNITR    schedule 20.10.2015
comment
@CppNITR Тогда посмотрите вопросы, которые я связал за несколько секунд до вашего комментария. Особенно второй полезен.   -  person anderas    schedule 20.10.2015
comment
@anderas stackoverflow.com/questions/31270810/ довольно близко, но ответ и ответы неудовлетворительны, не могли бы вы объяснить этот вопрос   -  person CppNITR    schedule 20.10.2015
comment
Всякий раз, когда я смотрю на одну из этих вещей и думаю о том, как это может повлиять на проектирование сложных (особенно универсальных) структур данных, я вздыхаю в отчаянии от того, насколько сложным на самом деле является язык C++.   -  person einpoklum    schedule 20.10.2015


Ответы (3)


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

auto r = ref(x);

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

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&.

Я надеюсь, что этого ответа более чем достаточно, чтобы объяснить ваши сомнения.

person Ankit Acharya    schedule 20.10.2015
comment
1: Нет, reference_wrapper можно переназначить, но он не может содержать ссылку более чем на один объект. 2/3: Справедливое замечание о том, где уместно .get() - но r без суффикса может использоваться так же, как T& в случаях, когда преобразование r operator может быть вызвано однозначно - так что нет необходимости для вызова .get() во многих случаях, включая несколько в вашем коде (который трудно читать из-за отсутствия пробелов). - person underscore_d; 11.12.2015
comment
@underscore_d reference_wrapper может содержать массив ссылок, если вы не уверены, то можете попробовать сами. Плюс .get() используется, когда вы хотите изменить значение объекта, который reference_wrapper держит, т.е. r=70 является незаконным, поэтому вы должны использовать r.get()=70. Попробуйте сами!!!!!! - person Ankit Acharya; 12.12.2015
comment
Покажите мне один reference_wrapper, содержащий более одной ссылки. - person underscore_d; 12.12.2015
comment
Не совсем. Во-первых, давайте использовать точные формулировки. Вы показываете reference_wrapper, содержащий ссылку на массив, а не reference_wrapper, который сам содержит ссылку более чем на один объект. Обертка содержит только одну ссылку. Во-вторых, я могу нормально получить нативную ссылку на массив — вы уверены, что не забыли (круглые скобки) вокруг int a[4]{1, 2, 3, 4}; int (&b)[4] = a;? reference_wrapper здесь не особенный, так как родной T& действительно работает. - person underscore_d; 13.12.2015
comment
Подобно тому, как массив используется для хранения одного или нескольких объектов, reference_wrapper для массива также будет содержать ссылки на несколько объектов. То, что он ссылается на массив, не означает, что он не ссылается более чем на один объект (на самом деле это действительно так). Конечно, это не особый случай, скорее array of references - это частный случай reference_wrapper (который я показал в своем ответе) - person Ankit Acharya; 13.12.2015
comment
Вместо этого reference_wrapper (т.е. r) содержит T& - разве он не содержит T* ? - person M.M; 13.12.2015
comment
@AnkitAcharya Да :-), но, если быть точным, эффективный результат в стороне, сам по себе относится только к одному объекту. В любом случае, вы, конечно, правы, что в отличие от обычного ref, wrapper может находиться в контейнере. Это удобно, но я думаю, что люди неправильно интерпретируют это как более продвинутое, чем оно есть на самом деле. Если мне нужен массив «ссылок», я обычно пропускаю посредника с vector<Item *>, к чему сводится wrapper... и надеюсь, что сторонники защиты указателей меня не найдут. Убедительные варианты использования для него различны и более сложны. - person underscore_d; 13.12.2015
comment
@M.M, да, на самом деле он содержит T*, поэтому он может ссылаться на более чем один объект, но в целом его поведение больше похоже на T&, чем на T* - person Ankit Acharya; 13.12.2015
comment
Reference_wrapper очень полезен, когда вы хотите эмулировать ссылку на объект, который можно скопировать. Но разве это не противоречит цели ссылки? Вы имеете в виду реальную вещь. Вот что такое ссылка. Ссылка, которую можно скопировать, кажется бессмысленной, потому что если вы хотите ее скопировать, то вам вообще не нужна ссылка, вы должны просто скопировать исходный объект. Это кажется ненужным уровнем сложности для решения проблемы, которая вообще не должна существовать. - person stu; 14.06.2017
comment
@stu Копирование ссылки полезно. Это связано с тем, что вы не копируете значение ссылки, а клонируете ссылку как (еще одну) ссылку на исходный объект. Это очень похоже на работу с указателем. Причина использования reference_wrapper<T> вместо T * состоит в том, чтобы сделать кристально ясным, что delete не следует использовать. - person Thomas Eding; 19.03.2019
comment
так.... почему нельзя просто сделать ссылку на ссылку? инт а = 1; интервал &b = а; интервал и с = б; - person stu; 21.03.2019

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&.

person Quentin    schedule 20.10.2015
comment
@CppNITR конечно! Дайте мне минутку, чтобы собрать небольшую демонстрацию :) - person Quentin; 20.10.2015
comment
как насчет разницы между T& & ref(T) - person CppNITR; 20.10.2015
comment
@CppNITR std::ref(T) возвращает std::reference_wrapper. Это немного больше, чем обернутый указатель, но библиотека распознает его как эй, я должен быть ссылкой! Пожалуйста, превратите меня обратно в один, как только вы закончите передавать меня. - person Quentin; 20.10.2015

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

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

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

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

  • поскольку это объект, назначаемый копией, его можно использовать в контейнерах или в других случаях, когда ссылки не разрешены.
  • благодаря своему operator T& () const noexcept; его можно использовать везде, где вы могли бы использовать ссылку, потому что он будет автоматически преобразован в него.
person Serge Ballesta    schedule 20.10.2015
comment
проголосовали в основном из-за упоминания operator T& (), о котором не было упомянуто в двух других ответах. - person metablaster; 08.10.2019