Різниця між std :: reference_wrapper та простим покажчиком?


99

Чому це потрібно мати std::reference_wrapper? Де його слід використовувати? Чим він відрізняється від простого вказівника? Як його ефективність порівнюється з простим покажчиком?


4
Це в основному вказівник, який ви використовуєте .замість->
MM

5
@MM Ні, використання .не працює так, як ви пропонуєте (якщо в якийсь момент пропозиція оператора не буде прийнята та інтегрована :))
Columbo

3
Саме такі питання роблять мене нещасним, коли мені доводиться працювати з новим C ++.
Нілс

Для подальшого використання Columbo використовується std :: reference_wrapper з get()функцією-членом або з неявним перетворенням назад в базовий тип.
Макс Барраклу

Відповіді:


88

std::reference_wrapperкорисна в поєднанні з шаблонами. Він обгортає об’єкт, зберігаючи вказівник на нього, дозволяючи перепризначити та копіювати, наслідуючи його звичайну семантику. Він також інструктує певні шаблони бібліотеки зберігати посилання замість об'єктів.

Розглянемо алгоритми в STL, які копіюють функтори: Ви можете уникнути цієї копії, просто передавши обгортку посилання, що стосується функтора, а не самого функтора.

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

Це працює, тому що ...

  • ... reference_wrapperсек перевантаженняoperator() так їх можна назвати так само , як функції об'єктів вони ставляться до:

    std::ref(myEngine)() // Valid expression, modifies myEngines state
  • … (Un) як звичайні посилання, копіювання (і призначення) 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посилається на a 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.

Відмінність від звичайних покажчиків

  • Немає додаткового рівня синтаксичної непрямості. Покажчики повинні бути зменшені, щоб отримати значення на об'єкт, на який вони посилаються; reference_wrappers мають неявний оператор перетворення, і його можна викликати як об'єкт, який вони обертають.

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

    std::reference_wrapper<int> r; // Invalid
  • Подібною є семантика дрібної копії: Покажчики та reference_wrappers можуть бути перепризначені.


Чи std::make_tuple(std::ref(i));перевершує std::make_tuple(&i);якимось чином?
Laurynas Lazauskas

6
@LaurynasLazauskas Це інакше. Останній, який ви показали, зберігає покажчик i, а не посилання на нього.
Коламбо

Гм ... Я думаю, я все ще не можу розрізнити цих двох так добре, як хотілося б ... Ну, дякую.
Laurynas Lazauskas

@Columbo Як можливий масив обгортки посилань, якщо вони не мають нульового стану? Чи не масиви зазвичай починаються з усіх елементів, встановлених в нульовий стан?
anatolyg

2
@anatolyg Що заважає вам ініціалізувати цей масив?
Коламбо

27

Принаймні, дві мотивуючі цілі 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)));
    

Чи можете ви надати приклад коду для першого випадку?
user1708860

1
@ user1708860: ти маєш на увазі відмінне від наведеного ...?
Дітмар Кюл

Я маю на увазі фактичний код, який відповідає std :: ref (весело), ​​тому що я не розумію, як використовується (якщо тільки забава не є об'єктом, а не функцією ...)
user1708860

2
@ user1708860: так, швидше за все, funце об'єкт функції (тобто об'єкт класу з оператором виклику функції), а не функція: якщо funтрапляється фактична функція, std::ref(fun)не має мети і робить код потенційно повільнішим.
Дітмар Кюл

23

Ще одна відмінність, що стосується коду 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

3
якщо це не код pre-c ++ 11, перший приклад повинен передбачати необов'язкові, невідомі значення, наприклад, для пошуку кешу на основі індексу. Було б добре, якби std надав нам щось стандартне, щоб представити
ненулеве

Це, мабуть, не так важливо в C ++ 11, де голі вказівники майже завжди будуть запозичувати значення.
Elling

reference_wrapperперевершує сировинні покажчики не тільки тому, що зрозуміло, що це не є власником, а й тому, що не може бути nullptr(без шеньяганів), і таким чином користувачі знають, що вони не можуть пройти nullptr(без шеньяганів), і ви знаєте, що вам не потрібно перевірити на це.
підкреслюй_d

19

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

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

1
@LaurynasLazauskas Можна безпосередньо викликати об’єкти функцій, що містяться в обгортці. Це також пояснено у моїй відповіді.
Коламбо

2
Оскільки реалізація посилань - це лише вказівник всередині, я не можу зрозуміти, чому обгортки додають будь-яку непряму або штрафну ефективність
Рига

4
Це не повинно бути більше непрямим, ніж проста посилання, коли мова заходить про код випуску
Рига

3
Я б очікував, що компілятор вбудує тривіальний reference_wrapperкод, зробивши його ідентичним коду, який використовує вказівник або посилання.
Девід Стоун

4
@LaurynasLazauskas: std::reference_wrapperмає гарантію, що об’єкт ніколи не буде нульовим. Розглянемо члена класу std::vector<T *>. Ви повинні вивчити весь код класу, щоб побачити, чи може цей об’єкт коли-небудь зберігати деякий nullptrвектор у векторі, тоді як з std::reference_wrapper<T>, у вас гарантовано є дійсні об’єкти.
Девід Стоун
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.