C ++ Diferença entre std :: ref (T) e T &?

93

Tenho algumas perguntas sobre este programa:

#include <iostream>
#include <type_traits>
#include <functional>
using namespace std;
template <typename T> void foo ( T x )
{
    auto r=ref(x);
    cout<<boolalpha;
    cout<<is_same<T&,decltype(r)>::value;
}
int main()
{
    int x=5;
    foo (x);
    return 0;
}

O resultado é:

false

Eu quero saber, se std::refnão retorna a referência de um objeto, então o que ele faz? Basicamente, qual é a diferença entre:

T x;
auto r = ref(x);

e

T x;
T &y = x;

Além disso, quero saber por que existe essa diferença? Por que precisamos std::refou std::reference_wrapperquando temos referências (ou seja T&)?

CppNITR
fonte
2
Possível duplicata de Como tr1 :: reference_wrapper é útil?
anderas 20/10/2015
Dica: o que acontece se você fizer x = y; em ambos os casos?
juanchopanza
2
Além de minha sinalização duplicada (último comentário): Veja, por exemplo, stackoverflow.com/questions/31270810/… e stackoverflow.com/questions/26766939/…
anderas
2
@anderas não se trata de utilidade, é principalmente sobre a diferença
CppNITR
@CppNITR Então veja as perguntas que vinculei alguns segundos antes de seu comentário. Especialmente o segundo é útil.
anderas

Respostas:

92

O Well refconstrói um objeto do reference_wrappertipo apropriado para manter uma referência a um objeto. O que significa quando você se inscreve:

auto r = ref(x);

Isso retorna a reference_wrappere não uma referência direta a x(ou seja T&). Este reference_wrapper(isto é r), em vez disso, é válido T&.

A reference_wrapperé muito útil quando você deseja emular a referencede um objeto que pode ser copiado (é tanto construtível por cópia quanto atribuível por cópia ).

Em C ++, uma vez que você criar uma referência (digamos y) a um objeto (digamos x), em seguida, ye xcompartilham o mesmo endereço de base . Além disso, ynão pode se referir a nenhum outro objeto. Além disso, você não pode criar uma matriz de referências, ou seja, um código como este gerará um erro:

#include <iostream>
using namespace std;

int main()
{
    int x=5, y=7, z=8;
    int& arr[] {x,y,z};    // error: declaration of 'arr' as array of references
    return 0;
}

No entanto, isso é legal:

#include <iostream>
#include <functional>  // for reference_wrapper
using namespace std;

int main()
{
    int x=5, y=7, z=8;
    reference_wrapper<int> arr[] {x,y,z};
    for (auto a: arr)
        cout << a << " ";
    return 0;
}
/* OUTPUT:
5 7 8
*/

Falando sobre seu problema com cout << is_same<T&,decltype(r)>::value;, a solução é:

cout << is_same<T&,decltype(r.get())>::value;  // will yield true

Deixe-me mostrar um programa:

#include <iostream>
#include <type_traits>
#include <functional>
using namespace std;

int main()
{
    cout << boolalpha;
    int x=5, y=7;
    reference_wrapper<int> r=x;   // or auto r = ref(x);
    cout << is_same<int&, decltype(r.get())>::value << "\n";
    cout << (&x==&r.get()) << "\n";
    r=y;
    cout << (&y==&r.get()) << "\n";
    r.get()=70;
    cout << y;
    return 0;
}
/* Ouput:
true
true
true
70
*/

Veja aqui, nós conhecemos três coisas:

  1. Um reference_wrapperobjeto (aqui r) pode ser usado para criar uma matriz de referências que não foi possível com T&.

  2. rrealmente age como uma referência real (veja como r.get()=70mudou o valor de y).

  3. rnão é o mesmo, T&mas r.get()é. Isso significa que rmantém, T&ou seja, como o nome sugere, é um invólucro em torno de uma referência T& .

Espero que esta resposta seja mais do que suficiente para esclarecer suas dúvidas.

Ankit Acharya
fonte
4
1: Não, a reference_wrapperpode ser reatribuído , mas não pode "conter referência a mais de um objeto". 2/3: Ponto justo sobre onde .get()é apropriado - mas sem sufixo r pode ser usado da mesma forma que T&nos casos em que ra conversão de operatorpode ser invocada sem ambigüidade - portanto, não há necessidade de chamar .get()em muitos casos, incluindo vários em seu código (que é difícil de ler devido à falta de espaços).
sublinhado_d
@underscore_d reference_wrapperpode conter um array de referências se você não tiver certeza, então você mesmo pode tentar. Plus .get()é usado quando você deseja alterar o valor do objeto que reference_wrapperestá segurando, ou seja, r=70é ilegal, então você deve usar r.get()=70. Experimente !!!!!!
Ankit Acharya
2
Na verdade não. Primeiro, vamos usar um texto preciso. Você está mostrando um reference_wrapper contendo uma referência a um array - não um reference_wrapper que contém "referência a mais de um objeto" . O wrapper contém apenas uma referência. Em segundo lugar, posso obter uma referência nativa para uma matriz muito bem - tem certeza de que não esqueceu os (parênteses) ao redor int a[4]{1, 2, 3, 4}; int (&b)[4] = a;? reference_wrapper não é especial aqui desde nativa T& faz trabalho.
underscore_d
1
@AnkitAcharya Sim :-) mas para ser preciso, resultado efetivo à parte, ele mesmo se refere a apenas um objeto. De qualquer forma, você está certo que, ao contrário de um árbitro normal, o wrapperpode ir em um contêiner. Isso é útil, mas acho que as pessoas interpretam mal como algo mais avançado do que realmente é. Se eu quiser um array de 'refs', geralmente pulo o intermediário vector<Item *>, que é o que se wrapperresume a ... e espero que os puristas anti-ponteiro não me encontrem. Os casos de uso convincentes são diferentes e mais complexos.
sublinhado_d
1
"Um reference_wrapper é muito útil quando você deseja emular uma referência de um objeto que pode ser copiado." Mas isso não anula o propósito de uma referência? Você está se referindo à coisa real. Isso é o que é uma referência. Uma referência que pode ser copiada, parece sem sentido, porque se você quiser copiá-la, então você não quer uma referência em primeiro lugar, você deve apenas copiar o objeto original. Parece uma camada desnecessária de complexidade para resolver um problema que não precisa existir em primeiro lugar.
stu
53

std::reference_wrapper é reconhecido pelas instalações padrão por ser capaz de passar objetos por referência em contextos de passagem por valor.

Por exemplo, std::bindpode receber o std::ref()para algo, transmiti-lo por valor e descompactá-lo em uma referência posteriormente.

void print(int i) {
    std::cout << i << '\n';
}

int main() {
    int i = 10;

    auto f1 = std::bind(print, i);
    auto f2 = std::bind(print, std::ref(i));

    i = 20;

    f1();
    f2();
}

Este snippet produz:

10
20

O valor de ifoi armazenado (tomado por valor) f1no ponto em que foi inicializado, mas f2manteve um std::reference_wrappervalor por e, portanto, se comporta como se tivesse recebido um int&.

Quentin
fonte
2
@CppNITR com certeza! Dê-me um momento para montar uma pequena demonstração :)
Quentin
1
e quanto à diferença entre T & & ref (T)
CppNITR
3
@CppNITR std::ref(T)retorna a std::reference_wrapper. É um pouco mais do que um ponteiro enrolado, mas é reconhecido pela biblioteca como "ei, eu deveria ser uma referência! Por favor, me transforme novamente em um quando você terminar de me passar".
Quentin de
39

Uma referência ( T&ou T&&) é um elemento especial na linguagem C ++. Permite manipular um objeto por referência e possui casos de uso especiais na linguagem. Por exemplo, você não pode criar um contêiner padrão para conter referências: vector<T&>está malformado e gera um erro de compilação.

Por std::reference_wrapperoutro lado, A é um objeto C ++ capaz de conter uma referência. Como tal, você pode usá-lo em contêineres padrão.

std::refé uma função padrão que retorna um std::reference_wrapperem seu argumento. Na mesma ideia, std::crefretorna std::reference_wrappera uma referência const.

Uma propriedade interessante de a std::reference_wrapperé que ele possui um operator T& () const noexcept;. Isso significa que mesmo se for um objeto verdadeiro , ele pode ser convertido automaticamente para a referência que está mantendo. Então:

  • por ser um objeto de cópia, pode ser usado em containers ou em outros casos onde referências não são permitidas
  • graças a operator T& () const noexcept;ele, pode ser usado em qualquer lugar onde você possa usar uma referência, pois será automaticamente convertido para ela.
Serge Ballesta
fonte
1
votado principalmente devido à menção de operator T& () quais outras 2 respostas não mencionaram.
metablaster