Разница между std :: reference_wrapper и простым указателем?

Зачем нужен std::reference_wrapper? Где его использовать? Чем он отличается от простого указателя? Как его производительность по сравнению с простым указателем?


person Laurynas Lazauskas    schedule 05.11.2014    source источник
comment
По сути, это указатель, с которым вы используете . вместо ->   -  person M.M    schedule 06.11.2014
comment
@ M.M Нет, использование . не работает так, как вы предлагаете (если в какой-то момент предложение точки оператора не будет принято и интегрировано :))   -  person Columbo    schedule 13.04.2016
comment
Именно такие вопросы меня расстраивают, когда мне приходится работать с новым C ++.   -  person Nils    schedule 25.07.2019
comment
Чтобы следовать за Columbo, std :: reference_wrapper используется с его функцией-членом get() или с его неявным преобразованием обратно в базовый тип.   -  person Max Barraclough    schedule 30.05.2020


Ответы (4)


std::reference_wrapper полезен в сочетании с шаблонами. Он обертывает объект, сохраняя указатель на него, позволяя переназначать и копировать, имитируя его обычную семантику. Он также указывает некоторым шаблонам библиотеки хранить ссылки вместо объектов.

Рассмотрим алгоритмы в STL, которые копируют функторы: вы можете избежать этой копии, просто передав ссылочную оболочку, ссылающуюся на функтор, а не на сам функтор:

unsigned arr[10];
std::mt19937 myEngine;
std::generate_n( arr, 10, std::ref(myEngine) ); // Modifies myEngine's state

Это работает, потому что…

  • reference_wrappers overload operator(), чтобы их можно было вызывать точно так же, как объекты функций, на которые они ссылаются к:

    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_wrappers имеют неявный оператор преобразования и могут вызываться как объект, который они обертывают.

    int i;
    int& ref = std::ref(i); // Okay
    
  • reference_wrappers, в отличие от указателей, не имеют нулевого состояния. Они должны быть инициализированы с помощью ссылки или другого reference_wrapper.

    std::reference_wrapper<int> r; // Invalid
    
  • Сходство заключается в семантике неглубокого копирования: указатели и reference_wrappers можно переназначать.

person Columbo    schedule 05.11.2014
comment
std::make_tuple(std::ref(i)); превосходит std::make_tuple(&i); в чем-то? - person Laurynas Lazauskas; 06.11.2014
comment
@LaurynasLazauskas Это другое. Последний, который вы показали, сохраняет указатель на i, а не ссылку на него. - person Columbo; 06.11.2014
comment
Хм ... Думаю, я до сих пор не могу различить этих двоих так хорошо, как хотелось бы ... Что ж, спасибо. - person Laurynas Lazauskas; 06.11.2014
comment
@Columbo Как возможен массив ссылочных оболочек, если у них нет нулевого состояния? Разве массивы обычно не начинаются со всех элементов, установленных в нулевое состояние? - person anatolyg; 06.11.2014
comment
@anatolyg Что мешает вам инициализировать этот массив? - person Columbo; 06.11.2014
comment
Указатели также могут быть объявлены так, чтобы не иметь нулевого состояния с помощью not_null - person Rufus; 16.02.2017
comment
@Woofas Это не имеет значения. Если я возьму параметр, являющийся указателем, я не знаю, был ли он инициализирован с помощью not_null. - person Columbo; 16.02.2017
comment
@Columbo Можно указать параметр как not_null, так что передача ему нулевого указателя станет недействительной. - person Rufus; 17.02.2017
comment
@Woofas Ой, извините, я думал, что это функция (я привык к UpperCamelCase для имен классов). Конечно, можно. - person Columbo; 17.02.2017
comment
@Woofas Интересно, но я не знаю, зачем в таком случае использовать указатель, а не просто ссылку. Объясняли ли они когда-нибудь, почему not_null предпочтительнее? Я этого не вижу, но я не очень изобретателен ... - person underscore_d; 20.05.2017
comment
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
comment
Думаю, optional<int&> лучше? - person Columbo; 01.09.2019

У std::reference_wrapper<T> есть как минимум две мотивирующие цели:

  1. Он предназначен для предоставления ссылочной семантики объектам, передаваемым в качестве параметра значения в шаблоны функций. Например, у вас может быть большой функциональный объект, который вы хотите передать в std::for_each(), который принимает параметр своего функционального объекта по значению. Чтобы избежать копирования объекта, вы можете использовать

    std::for_each(begin, end, std::ref(fun));
    

    Передача аргументов как std::reference_wrapper<T> в выражение std::bind() довольно распространена для привязки аргументов по ссылке, а не по значению.

  2. При использовании std::reference_wrapper<T> с std::make_tuple() соответствующий элемент кортежа становится T&, а не T:

    T object;
    f(std::make_tuple(1, std::ref(object)));
    
person Dietmar Kühl    schedule 05.11.2014
comment
Не могли бы вы привести пример кода для первого случая? - person user1708860; 06.11.2014
comment
@ user1708860: ты имеешь ввиду не тот, что дан ...? - person Dietmar Kühl; 06.11.2014
comment
Я имею в виду фактический код, который соответствует std :: ref (fun), потому что я не понимаю, как он используется (если только fun не является объектом, а не функцией ...) - person user1708860; 06.11.2014
comment
@ user1708860: да, скорее всего 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
person Edward Loper    schedule 31.03.2015
comment
Если это не код до C ++ 11, первый пример должен подразумевать необязательные, не принадлежащие к собственности значения, например, для поиска в кеше на основе индекса. Было бы неплохо, если бы std предоставил нам что-то стандартное для представления ненулевого, принадлежащего значения (уникальные и общие варианты) - person Bwmat; 24.12.2016
comment
Возможно, это не так важно в C ++ 11, где голые указатели почти всегда будут заимствованными значениями. - person bjaastad_e; 23.04.2020
comment
reference_wrapper превосходит необработанные указатели не только потому, что ясно, что они не принадлежат, но и потому, что они не могут быть nullptr (без махинаций), и, таким образом, пользователи знают, что они не могут пройти nullptr (без махинаций), и вы знаете, что это не так. Не нужно это проверять. - person underscore_d; 23.07.2020
comment
@underscore_d Что вы имеете в виду без махинаций? Возможно ли с махинациями? Я очень любопытный - person Spyros Mourelatos; 27.07.2021
comment
@SpyrosMourelatos Возможно, хотя и с неопределенным поведением и, следовательно, совершенно нецелесообразно ... формировать «нулевую ссылку». Затем можно засунуть получившуюся недопустимую ссылку в reference_wrapper. Но создание / использование таких ссылок ведет к неопределенному поведению, поэтому я - слишком щедрый! - навешивать на это ярлык махинаций. См. Возможна ли пустая ссылка? - person underscore_d; 28.07.2021
comment
@underscore_d Большое спасибо, с вашей помощью я наконец могу заставить своих коллег на работе покончить жизнь самоубийством. :П - person Spyros Mourelatos; 29.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
person Barry    schedule 05.11.2014
comment
Зачем использовать std::vector<std::reference_wrapper<T>> vec; вместо std::vector<T*> vec;? - person Laurynas Lazauskas; 06.11.2014
comment
@LaurynasLazauskas Можно напрямую вызывать объекты функций, содержащиеся в оболочке. Это также объясняется в моем ответе. - person Columbo; 06.11.2014
comment
Поскольку эталонная реализация - это просто указатель внутри, я не могу понять, почему оболочки добавляют какое-либо косвенное обращение или штраф за производительность. - person Riga; 06.11.2014
comment
Когда дело доходит до кода выпуска, это должно быть не более косвенным обращением, чем простая ссылка. - person Riga; 06.11.2014
comment
Я ожидал, что компилятор встроит тривиальный код reference_wrapper, сделав его идентичным коду, который использует указатель или ссылку. - person David Stone; 25.11.2014
comment
@LaurynasLazauskas: std::reference_wrapper гарантирует, что объект никогда не будет нулевым. Рассмотрим члена класса std::vector<T *>. Вы должны изучить весь код класса, чтобы увидеть, сможет ли этот объект когда-либо сохранить nullptr в векторе, тогда как с std::reference_wrapper<T> вы гарантированно получите действительные объекты. - person David Stone; 25.11.2014
comment
Я не понимаю, насколько ваши причуды причудами. Оба они по определению: (1) reference_wrapper должен ссылаться на свой объект через указатель (потому что он должен быть назначаемым, иначе нам бы не понадобился этот класс и т. Д.), И это все, что ему нужно. (2) Реальная ссылка не имеет sizeof в зависимости от языка, поэтому оператор всегда возвращает sizeof указанный тип (тот факт, что ссылка внутри объекта реализована как указатель и очевидна в sizeof указанном объекте, является деталью реализации) . - person underscore_d; 24.09.2018
comment
@underscore_d Я не понимаю, как что-то истинное по определению каким-то образом мешает тому, чтобы это было причудой. Учитывая vector<int> v;, std::vector x{v, v} и std::vector y{v} имеют разные типы. Это правда по определению. Но также причуда правил CTAD. - person Barry; 24.09.2018