T&&(doble ampersand) en C++11

T&&(doble ampersand) en C++11

En este artículo, hemos explorado la idea de T&&(doble ampersand) en C++11. T&&se introdujo por primera vez en el estándar C++ 11 que declara una referencia de valor r.

Referencias

Hay dos referencias de valor-

  1. referencia de valor
  2. referencia de valor

"L" significa izquierda en la referencia de valor l y "R" significa derecha en la referencia de valor r. Lo que debe recordar es que estas son propiedades de expresiones y no de objetos.

lvalue continúa existiendo más allá de una expresión, mientras que rvalue hasta que finaliza una expresión porque l value es un objeto mutable declarado en el lado izquierdo y derecho de una asignación, mientras que rvalue es un objeto temporal asignado a una asignación.

En C++ 11, lvalue puede enlazar rvalue.Ex.

T&& a=T();

Código para entender referencias-

#include <iostream>
int main() {
  int a = 1; // a is an lvalue
  int &ref1 = a; // ref1 is a lvalue reference 
  std::cout << "The address of ref1 is same as a: " << &a 

    << ':' << &ref1 << '\n';
  // int &&rref = a; // rvalue reference to type 'int' cannot bind to 

                            // lvalue of type 'int'

  int &&ref2 = 10;
  std::cout << "Value of rref: " << ref2 << '\n';
  // Can't take the address of an rvalue, but can take the address
  // of an rvalue reference variable because it has a name :)
  std::cout << "Address of ref2: " << &ref2 << '\n'; 

  return 0;
}

Salida-

The address of ref1 is same as x: 0x7ffc7ccd0790:0x7ffc7ccd0790
Value of ref2: 10
Address of ref2: 0x7ffc7ccd0794

Con el código anterior podemos tener una pequeña idea de lo que realmente hacen referencia lvalue y rvalue. Podemos mejorar aún más nuestra referencia de conocimiento de T&&usándolo en una función.

Código

printR (const std::string& s)  {
    std::cout << "rvalue reference: " << s << '\n';
}
printR (std::string&& str) {
    std::cout << "rvalue reference: " << s << '\n';
}

La primera función aceptará cualquier argumento, ya sea lvalue o referencia de rvalue, mientras que la segunda aceptará solo rvalues, excepto las referencias mutables de rvalue.

Ahora, llamaremos a la función esas funciones para ver qué devuelven.

Código

std::string m = "OpenGenus";
printR(m); // calls function #1, taking an lvalue reference
printR("Joe"); // calls function #2, taking an rvalue reference
printR(std::string("Carl")) // function #2 too!

Las referencias de Rvalue nos dicen si la variable de referencia es un objeto temporal o permanente.
A continuación se implementa todo el programa mencionado anteriormente en partes-

Código

#include <iostream>
using namespace std;
string printR (const string& s)  {

    cout << "rvalue reference: " << s << '\n';

}
string printR (string&& s) {

    cout << "rvalue reference: " << s << '\n';

}
int main() {
   string a = "OpenGenus";
   string s1=printR(a); 
   string s2=printR("Joe"); // calls function #2, taking an rvalue reference
   string s3=printR(string("Carl")) ;// function #2 too!
   cout<<s1;
   cout<<s2;
   cout<<s3;
    return 0;
}

Salida

rvalue reference: OpenGenus
rvalue reference: Joe
rvalue reference: Carl

Las referencias de Rvalue proporcionan lo siguiente-

  1. Mover semántica-

Usar el código anterior tiene su efecto secundario, pero en realidad no importa hasta que usamos un valor muy grande que finalmente distingue las referencias de valor r y valor l. Entonces, mover objetos grandes nos cuesta mucha más memoria, ¿por qué no usamos algo que utiliza la memoria utilizada por el rvalue temporal.

Para aplicar esto, necesitamos usar el constructor de movimiento y la asignación de movimiento que toma la referencia de rvalue y funciones de movimiento como una copia, lo cual es bueno para eliminar la biblioteca estándar de copias.

Ej.

f(f const& a)
{
    this->length = a.length;
    this->ptr = new int[a.length];
    copy(a.ptr, a.ptr + a.length, this->ptr);
}

Ahora, si sobrecargamos nuestro constructor-

f(f&& a)
{
    this->length = a.length;
    this->ptr = a.ptr;
    a.length = 0;
    a.ptr = nullptr;
}

Ahora, el constructor de movimiento en realidad modifica sus argumentos, lo que elimina sus copias y mueve el constructor de manera eficiente.

2.Reenvío perfecto-

Las referencias de Rvalue nos permiten reenviar argumentos para funciones de plantilla.
Las funciones de plantilla son aquellas funciones que adaptan más de un tipo para aumentar la reutilización. Ej.

template <typename T1, typename A1>
utr<T1> factory(A1& a1)
{
  return unique_ptr<T1>(new T1(forward<A1>(a1)));
}

Propiedades importantes de las referencias-

Por la explicación anterior podemos entender lo siguiente -

  1. Lvalue es cualquier cosa que tenga un nombre. Ex.int a =1 ,
    int &r =a.
    2.Lvalue es un objeto mutable que tiene una dirección de memoria.
    3.Rvalue es un objeto temporal que se encuentra en el lado derecho de una asignación.
    4.Lvakue se puede colocar en cualquier lado de la asignación a la derecha o a la izquierda.

Con este artículo en OpenGenus, debe tener la idea completa de T&&(doble ampersand) en C++11.