Подтвердить что ты не робот

С++ Разница между 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&)?

4b9b3361

Ответ 1

Также 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 действует как реальная ссылка (посмотрите, как r.get()=70 изменило значение y).

  3. r не совпадает с T& но r.get() есть. Это означает, что r содержит T& то есть, как следует из его названия, является оберткой вокруг ссылочного T&.

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

Ответ 2

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

Ответ 3

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

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

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

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

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