Зачем нужен std::reference_wrapper
? Где его использовать? Чем он отличается от простого указателя? Как его производительность по сравнению с простым указателем?
Разница между std :: reference_wrapper и простым указателем?
Ответы (4)
std::reference_wrapper
полезен в сочетании с шаблонами. Он обертывает объект, сохраняя указатель на него, позволяя переназначать и копировать, имитируя его обычную семантику. Он также указывает некоторым шаблонам библиотеки хранить ссылки вместо объектов.
Рассмотрим алгоритмы в STL, которые копируют функторы: вы можете избежать этой копии, просто передав ссылочную оболочку, ссылающуюся на функтор, а не на сам функтор:
unsigned arr[10];
std::mt19937 myEngine;
std::generate_n( arr, 10, std::ref(myEngine) ); // Modifies myEngine's state
Это работает, потому что…
…
reference_wrapper
s overloadoperator()
, чтобы их можно было вызывать точно так же, как объекты функций, на которые они ссылаются к:std::ref(myEngine)() // Valid expression, modifies myEngines state
… (Не) как обычные ссылки, копирование (и присвоение)
reference_wrappers
просто присваивает указатель.int i, j; auto r = std::ref(i); // r refers to i r = std::ref(j); // Okay; r refers to j r = std::cref(j); // Error: Cannot bind reference_wrapper<int> to <const int>
Копирование ссылочной оболочки практически эквивалентно копированию указателя, которое настолько дешево, насколько это возможно. Все вызовы функций, связанные с его использованием (например, те, которые используются для operator()
), должны быть просто встроены, поскольку они являются однострочными.
reference_wrapper
создаются с помощью std::ref
и std::cref
:
int i;
auto r = std::ref(i); // r is of type std::reference_wrapper<int>
auto r2 = std::cref(i); // r is of type std::reference_wrapper<const int>
Аргумент шаблона определяет тип и cv-квалификацию объекта, на который имеется ссылка; r2
относится к const int
и дает только ссылку на const int
. Вызовы ссылочных оболочек с const
функторами в них будут вызывать только const
функцию-член operator()
s.
Инициализаторы Rvalue запрещены, поскольку их разрешение принесет больше вреда, чем пользы. Поскольку rvalues все равно будут перемещены (и с гарантированным исключением копии даже этого частично удалось избежать), мы не улучшаем семантику; мы можем ввести висячие указатели, поскольку оболочка ссылок не продлевает время жизни указателя.
Взаимодействие с библиотекой
Как упоминалось ранее, можно указать make_tuple
сохранить ссылку в результирующем tuple
, передав соответствующий аргумент через reference_wrapper
:
int i;
auto t1 = std::make_tuple(i); // Copies i. Type of t1 is tuple<int>
auto t2 = std::make_tuple(std::ref(i)); // Saves a reference to i.
// Type of t2 is tuple<int&>
Обратите внимание, что это немного отличается от forward_as_tuple
: здесь rvalues в качестве аргументов не разрешены.
std::bind
демонстрирует то же поведение: он не копирует аргумент, но сохраняет ссылку, если она а reference_wrapper
. Полезно, если этот аргумент (или функтор!) Не нужно копировать, но остается в области видимости, пока используется bind
-функтор.
Отличие от обычных указателей
Дополнительного уровня синтаксической косвенности нет. Указатели должны быть разыменованы, чтобы получить l-значение для объекта, на который они ссылаются;
reference_wrapper
s имеют неявный оператор преобразования и могут вызываться как объект, который они обертывают.int i; int& ref = std::ref(i); // Okay
reference_wrapper
s, в отличие от указателей, не имеют нулевого состояния. Они должны быть инициализированы с помощью ссылки или другогоreference_wrapper
.std::reference_wrapper<int> r; // Invalid
Сходство заключается в семантике неглубокого копирования: указатели и
reference_wrapper
s можно переназначать.
std::make_tuple(std::ref(i));
превосходит std::make_tuple(&i);
в чем-то?
- person Laurynas Lazauskas; 06.11.2014
i
, а не ссылку на него.
- person Columbo; 06.11.2014
not_null
.
- person Columbo; 16.02.2017
not_null
, так что передача ему нулевого указателя станет недействительной.
- person Rufus; 17.02.2017
not_null
предпочтительнее? Я этого не вижу, но я не очень изобретателен ...
- person underscore_d; 20.05.2017
reference_wrappers, unlike pointers, don't have a null state. They have to be initialized with either a reference or another reference_wrapper.
Начиная с C ++ 17, вы можете использовать std::optional
, чтобы иметь ссылку без инициализации: std::optional<std::reference_wrapper<int>> x; auto y = 4; x = y;
Однако доступ к ней немного многословен: std::cout << x.value().get();
- person dteod; 01.09.2019
optional<int&>
лучше?
- person Columbo; 01.09.2019
У std::reference_wrapper<T>
есть как минимум две мотивирующие цели:
Он предназначен для предоставления ссылочной семантики объектам, передаваемым в качестве параметра значения в шаблоны функций. Например, у вас может быть большой функциональный объект, который вы хотите передать в
std::for_each()
, который принимает параметр своего функционального объекта по значению. Чтобы избежать копирования объекта, вы можете использоватьstd::for_each(begin, end, std::ref(fun));
Передача аргументов как
std::reference_wrapper<T>
в выражениеstd::bind()
довольно распространена для привязки аргументов по ссылке, а не по значению.При использовании
std::reference_wrapper<T>
сstd::make_tuple()
соответствующий элемент кортежа становитсяT&
, а неT
:T object; f(std::make_tuple(1, std::ref(object)));
fun
- это объект функции (т.е. объект класса с оператором вызова функции), а не функция: если fun
является реальной функцией, std::ref(fun)
не имеет цели и потенциально замедляет выполнение кода.
- person Dietmar Kühl; 06.11.2014
Еще одно отличие с точки зрения самодокументирующегося кода состоит в том, что использование reference_wrapper
по существу отрицает право собственности на объект. Напротив, unique_ptr
утверждает право собственности, в то время как пустой указатель может принадлежать или не принадлежать (это невозможно узнать, не глядя на множество связанного кода):
vector<int*> a; // the int values might or might not be owned
vector<unique_ptr<int>> b; // the int values are definitely owned
vector<reference_wrapper<int>> c; // the int values are definitely not owned
reference_wrapper
превосходит необработанные указатели не только потому, что ясно, что они не принадлежат, но и потому, что они не могут быть nullptr
(без махинаций), и, таким образом, пользователи знают, что они не могут пройти nullptr
(без махинаций), и вы знаете, что это не так. Не нужно это проверять.
- person underscore_d; 23.07.2020
reference_wrapper
. Но создание / использование таких ссылок ведет к неопределенному поведению, поэтому я - слишком щедрый! - навешивать на это ярлык махинаций. См. Возможна ли пустая ссылка?
- person underscore_d; 28.07.2021
Вы можете думать об этом как о удобной оболочке вокруг ссылок, чтобы вы могли использовать их в контейнерах.
std::vector<std::reference_wrapper<T>> vec; // OK - does what you want
std::vector<T&> vec2; // Nope! Will not compile
По сути, это CopyAssignable
версия T&
. Каждый раз, когда вам нужна ссылка, но она должна быть назначаемой, используйте std::reference_wrapper<T>
или его вспомогательную функцию std::ref()
. Или используйте указатель.
Прочие причуды: sizeof
:
sizeof(std::reference_wrapper<T>) == sizeof(T*) // so 8 on a 64-bit box
sizeof(T&) == sizeof(T) // so, e.g., sizeof(vector<int>&) == 24
И сравнение:
int i = 42;
assert(std::ref(i) == std::ref(i)); // ok
std::string s = "hello";
assert(std::ref(s) == std::ref(s)); // compile error
std::vector<std::reference_wrapper<T>> vec;
вместо std::vector<T*> vec;
?
- person Laurynas Lazauskas; 06.11.2014
reference_wrapper
, сделав его идентичным коду, который использует указатель или ссылку.
- person David Stone; 25.11.2014
std::reference_wrapper
гарантирует, что объект никогда не будет нулевым. Рассмотрим члена класса std::vector<T *>
. Вы должны изучить весь код класса, чтобы увидеть, сможет ли этот объект когда-либо сохранить nullptr
в векторе, тогда как с std::reference_wrapper<T>
вы гарантированно получите действительные объекты.
- person David Stone; 25.11.2014
reference_wrapper
должен ссылаться на свой объект через указатель (потому что он должен быть назначаемым, иначе нам бы не понадобился этот класс и т. Д.), И это все, что ему нужно. (2) Реальная ссылка не имеет sizeof
в зависимости от языка, поэтому оператор всегда возвращает sizeof
указанный тип (тот факт, что ссылка внутри объекта реализована как указатель и очевидна в sizeof
указанном объекте, является деталью реализации) .
- person underscore_d; 24.09.2018
vector<int> v;
, std::vector x{v, v}
и std::vector y{v}
имеют разные типы. Это правда по определению. Но также причуда правил CTAD.
- person Barry; 24.09.2018
.
вместо->
- person M.M   schedule 06.11.2014.
не работает так, как вы предлагаете (если в какой-то момент предложение точки оператора не будет принято и интегрировано :)) - person Columbo   schedule 13.04.2016get()
или с его неявным преобразованием обратно в базовый тип. - person Max Barraclough   schedule 30.05.2020