c++ c++11 move-semantics c++-faq qualifiers

c++ - ¿Qué es "rvalue reference for*this"?



c++11 move-semantics (3)

Encontré una propuesta llamada "rvalue reference for * this" en la página de estado de C ++ 11 de Clang.

He leído bastante acerca de las referencias de valores y las he entendido, pero no creo que sepa esto. Tampoco pude encontrar muchos recursos en la web usando los términos.

Hay un enlace al documento de propuesta en la página: N2439 (Extender la semántica de movimiento a * esto), pero tampoco recibo muchos ejemplos de eso.

¿De qué se trata esta característica?


Digamos que tiene dos funciones en una clase, ambas con el mismo nombre y firma. Pero una de ellas es declarada const :

void SomeFunc() const; void SomeFunc();

Si una instancia de clase no es const , la resolución de sobrecarga seleccionará preferentemente la versión no constante. Si la instancia es const , el usuario solo puede llamar a la versión const . Y this puntero es un puntero de const , por lo que la instancia no se puede cambiar.

Lo que hace "r-value reference for this` es permitirle agregar otra alternativa:

void RValueFunc() &&;

Esto le permite tener una función a la que solo se puede llamar si el usuario la llama a través de un valor-r adecuado. Así que si esto está en el tipo Object :

Object foo; foo.RValueFunc(); //error: no `RValueFunc` version exists that takes `this` as l-value. Object().RValueFunc(); //calls the non-const, && version.

De esta manera, puede especializar el comportamiento en función de si se accede al objeto a través de un valor r o no.

Tenga en cuenta que no puede sobrecargarse entre las versiones de referencia de valor r y las versiones que no son de referencia. Es decir, si tiene un nombre de función miembro, todas sus versiones usan los calificadores l / r-value en this , o ninguno de ellos lo hace. No puedes hacer esto:

void SomeFunc(); void SomeFunc() &&;

Tienes que hacer esto:

void SomeFunc() &; void SomeFunc() &&;

Tenga en cuenta que esta declaración cambia el tipo de *this . Esto significa que las versiones && todos los miembros de acceso como referencias de valor r. Así que es posible moverse fácilmente desde dentro del objeto. El ejemplo dado en la primera versión de la propuesta es (nota: lo siguiente puede no ser correcto con la versión final de C ++ 11; es directamente de la propuesta inicial "r-value from this"):

class X { std::vector<char> data_; public: // ... std::vector<char> const & data() const & { return data_; } std::vector<char> && data() && { return data_; } }; X f(); // ... X x; std::vector<char> a = x.data(); // copy std::vector<char> b = f().data(); // move


Hay un caso de uso adicional para el formulario de calificador ref del calificador. C ++ 98 tiene un lenguaje que permite llamar a funciones miembro no const para instancias de clase que son valores. Esto conduce a todo tipo de rarezas que van en contra del concepto mismo de rvalidad y se desvía de cómo funcionan los tipos integrados:

struct S {  S& operator ++(); S* operator &(); }; S() = S(); // rvalue as a left-hand-side of assignment! S& foo = ++S(); // oops, dangling reference &S(); // taking address of rvalue...

Los ref-calificadores de Lvalue resuelven estos problemas:

struct S { S& operator ++() &; S* operator &() &; const S& operator =(const S&) &; };

Ahora los operadores funcionan como los de los tipos incorporados, aceptando solo valores de l.


Primero, "ref-qualifiers for * this" es solo una "declaración de marketing". El tipo de *this nunca cambia, vea la parte inferior de esta publicación. Sin embargo, es mucho más fácil de entender con esta redacción.

A continuación, el siguiente código elige la función a llamar en función del calificador ref del "parámetro de objeto implícito" de la función :

// t.cpp #include <iostream> struct test{ void f() &{ std::cout << "lvalue object/n"; } void f() &&{ std::cout << "rvalue object/n"; } }; int main(){ test t; t.f(); // lvalue test().f(); // rvalue }

Salida:

$ clang++ -std=c++0x -stdlib=libc++ -Wall -pedantic t.cpp $ ./a.out lvalue object rvalue object

Todo se hace para permitirle aprovechar el hecho de que el objeto al que se llama la función es un rvalue (temporal sin nombre, por ejemplo). Tome el siguiente código como un ejemplo adicional:

struct test2{ std::unique_ptr<int[]> heavy_resource; test2() : heavy_resource(new int[500]) {} operator std::unique_ptr<int[]>() const&{ // lvalue object, deep copy std::unique_ptr<int[]> p(new int[500]); for(int i=0; i < 500; ++i) p[i] = heavy_resource[i]; return p; } operator std::unique_ptr<int[]>() &&{ // rvalue object // we are garbage anyways, just move resource return std::move(heavy_resource); } };

Esto puede ser un poco artificial, pero debe tener la idea.

Tenga en cuenta que puede combinar los calificadores cv ( const y volatile ) y los calificadores ref ( & && ).

Nota: ¡Muchas explicaciones de citas estándar y resolución de sobrecarga después de aquí!

† Para entender cómo funciona esto y por qué la respuesta de @Nicol Bolas es al menos parcialmente incorrecta, tenemos que profundizar un poco en el estándar de C ++ (la parte que explica por qué la respuesta de @Nolol está mal está al final, si Sólo interesado en eso).

La función que se va a llamar está determinada por un proceso llamado resolución de sobrecarga . Este proceso es bastante complicado, así que solo tocaremos la parte que es importante para nosotros.

Primero, es importante ver cómo funciona la resolución de sobrecarga para las funciones miembro:

§13.3.1 [over.match.funcs]

p2 El conjunto de funciones candidatas puede contener funciones miembro y no miembro que se resolverán contra la misma lista de argumentos. De modo que las listas de parámetros y argumentos son comparables dentro de este conjunto heterogéneo, se considera que una función miembro tiene un parámetro adicional, denominado parámetro objeto implícito, que representa el objeto para el que se ha llamado la función miembro . [...]

p3 De manera similar, cuando sea apropiado, el contexto puede construir una lista de argumentos que contenga un argumento de objeto implícito para denotar el objeto a operar.

¿Por qué incluso necesitamos comparar las funciones miembro y no miembro? Sobrecarga del operador, por eso. Considera esto:

struct foo{ foo& operator<<(void*); // implementation unimportant }; foo& operator<<(foo&, char const*); // implementation unimportant

Ciertamente querrás que lo siguiente llame a la función gratuita, ¿no es así?

char const* s = "free foo!/n"; foo f; f << s;

Es por eso que las funciones miembro y no miembro se incluyen en el llamado conjunto de sobrecarga. Para hacer la resolución menos complicada, existe la parte en negrita de la cita estándar. Además, este es el bit importante para nosotros (la misma cláusula):

p4 Para funciones miembro no estáticas, el tipo del parámetro objeto implícito es

  • “Lvalue reference to cv X ” para funciones declaradas sin un ref-qualifier o con el & ref-qualifier

  • “Rvalue reference to cv X ” para las funciones declaradas con el calificador && ref-qualifier

donde X es la clase de la cual la función es miembro y cv es la calificación cv en la declaración de la función miembro. [...]

p5 Durante la resolución de sobrecarga [...] [t] l parámetro de objeto [...] implícito conserva su identidad ya que las conversiones en el argumento correspondiente obedecerán estas reglas adicionales:

  • no se puede introducir ningún objeto temporal para mantener el argumento del parámetro objeto implícito; y

  • no se pueden aplicar conversiones definidas por el usuario para lograr una coincidencia de tipo con él

[...]

(El último bit simplemente significa que no se puede engañar a la resolución de sobrecarga basada en conversiones implícitas del objeto al que se llama una función miembro (u operador)).

Tomemos el primer ejemplo en la parte superior de esta publicación. Después de la transformación antes mencionada, el conjunto de sobrecarga se ve algo así:

void f1(test&); // will only match lvalues, linked to ''void test::f() &'' void f2(test&&); // will only match rvalues, linked to ''void test::f() &&''

Luego, la lista de argumentos, que contiene un argumento de objeto implícito , se compara con la lista de parámetros de cada función contenida en el conjunto de sobrecarga. En nuestro caso, la lista de argumentos solo contendrá ese argumento de objeto. Veamos cómo se ve eso:

// first call to ''f'' in ''main'' test t; f1(t); // ''t'' (lvalue) can match ''test&'' (lvalue reference) // kept in overload-set f2(t); // ''t'' not an rvalue, can''t match ''test&&'' (rvalue reference) // taken out of overload-set

Si, después de probar todas las sobrecargas en el conjunto, solo queda una, la resolución de sobrecarga tuvo éxito y se llama a la función vinculada a esa sobrecarga transformada. Lo mismo ocurre con la segunda llamada a ''f'':

// second call to ''f'' in ''main'' f1(test()); // ''test()'' not an lvalue, can''t match ''test&'' (lvalue reference) // taken out of overload-set f2(test()); // ''test()'' (rvalue) can match ''test&&'' (rvalue reference) // kept in overload-set

Sin embargo, tenga en cuenta que, si no hubiéramos proporcionado ningún ref-calificador (y, como tal, no haya sobrecargado la función), f1 coincidiría con un valor (aún §13.3.1 ):

p5 [...] Para funciones miembro no estáticas declaradas sin un ref-calificador , se aplica una regla adicional:

  • incluso si el parámetro objeto implícito no está constalificado, se puede vincular un valor de r al parámetro siempre que, en todos los demás aspectos, el argumento se pueda convertir al tipo del parámetro objeto implícito.

struct test{ void f() { std::cout << "lvalue or rvalue object/n"; } }; int main(){ test t; t.f(); // OK test().f(); // OK too }

Ahora, en por qué la respuesta de @ Nicol es al menos parcialmente incorrecta. Él dice:

Tenga en cuenta que esta declaración cambia el tipo de *this .

Eso es incorrecto, *this siempre es un valor lime:

§5.3.1 [expr.unary.op] p1

El operador unario * realiza una indirección : la expresión a la que se aplica debe ser un puntero a un tipo de objeto o un puntero a un tipo de función y el resultado es un lvalor que se refiere al objeto o función al que apunta la expresión.

§9.3.2 [class.this] p1

En el cuerpo de una función miembro no estática (9.3), la palabra clave es una expresión prvalue cuyo valor es la dirección del objeto para el que se llama la función. El tipo de this en una función miembro de una clase X es X* . [...]