С++ Разница между 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&
)?
Ответы
Ответ 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
*/
Смотрите здесь мы узнаем три вещи:
-
Объект reference_wrapper
(здесь r
) может использоваться для создания массива ссылок, что было невозможно с T&
.
-
r
самом деле r
действует как реальная ссылка (посмотрите, как r.get()=70
изменило значение y
).
-
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;
, его можно использовать везде, где вы могли бы использовать ссылку, потому что он будет автоматически преобразован в него.