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

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

Почему существует необходимость иметь std::reference_wrapper? Где его использовать? Как это отличается от простого указателя? Как его производительность сравнивается с простым указателем?

4b9b3361

Ответ 1

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

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

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

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

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

    std::ref(myEngine)() // Valid expression, modifies myEngines state
    
  • ... (un) как обычные ссылки, копирование (и присвоение) reference_wrappers просто назначает pointee.

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

Отличие от обычных указателей

  • Дополнительного уровня синтаксической косвенности нет. Указатели должны быть разыменованы, чтобы получить lvalue к объекту, на который они ссылаются; reference_wrapper имеют неявный оператор преобразования и могут быть вызваны как объект, который они обертывают.

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

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

Ответ 2

Есть, по крайней мере, две мотивирующие цели 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)));
    

Ответ 3

Вы можете думать об этом как об удобном обертке вокруг ссылок, чтобы вы могли использовать их в контейнерах.

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(). Или используйте указатель.

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

Другие особенности:

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

Ответ 4

Другим отличием, в терминах самодокументирующего кода, является то, что использование 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