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

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

68

Решение

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

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

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

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

  • reference_wrappers перегрузка 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_wrappers создаются через 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 запрещены, поскольку их разрешение принесет больше вреда, чем пользы. Так как значения будут перемещены в любом случае (и с гарантированная копия elision даже это частично избегается), мы не улучшаем семантику; мы можем ввести висячие указатели, так как эталонная оболочка не продлевает срок жизни пуантиста.

Взаимодействие библиотеки

Как упоминалось ранее, можно проинструктировать 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Здесь значения в качестве аргументов недопустимы.

std::bind показывает то же поведение: он не будет копировать аргумент, но будет хранить ссылку, если это reference_wrapper, Полезно, если этот аргумент (или функтор!) Не нужно копировать, но он остается в области действия, пока bind-функционер используется.

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

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

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

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

70

Другие решения

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

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

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

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
16
По вопросам рекламы [email protected]