c++ c++11 rvalue-reference

c++ - ¿Para qué son las "referencias de valor para*esto"?



c++11 rvalue-reference (3)

Algunas operaciones pueden ser más eficientes cuando se invocan rvalores, por lo que la sobrecarga en la categoría de valor de *this permite que la implementación más eficiente se use automáticamente, por ejemplo

struct Buffer { std::string m_data; public: std::string str() const& { return m_data; } // copies data std::string str()&& { return std::move(m_data); } // moves data };

(Esta optimización podría hacerse para std::ostringstream , pero no se ha propuesto formalmente AFAIK).

Algunas operaciones no tienen sentido invocar rvalues, por lo que la sobrecarga en *this permite que se elimine el formulario de rvalue:

struct Foo { void mutate()&; void mutate()&& = delete; };

Todavía no he necesitado usar esta función, pero tal vez encuentre más usos ahora que los dos compiladores que me importan lo soportan.

¿Cuáles son los casos de uso más comunes de "rvalue references for * this" que el estándar también llama calificadores de referencia para funciones miembro?

Por cierto, aquí hay una muy buena explicación sobre esta característica del lenguaje.


Cuando se llama, cada función miembro tiene un parámetro de objeto implícito que *this referencia.

Entonces (a) estas sobrecargas de funciones normales:

void f(const T&); void f(T&&);

cuando se llama como f(x) ; y (b) estas funciones de miembro sobrecargan:

struct C { void f() const &; void f() &&; };

cuando se llama como xf() , ambos despachos (a) y (b) tienen una viabilidad y clasificación similares.

Así que los casos de uso son esencialmente los mismos. Son para apoyar la optimización semántica en movimiento. En la función de miembro rvalue, básicamente puedes saquear los recursos de los objetos porque sabes que es un objeto que está expirando (está a punto de ser eliminado):

int main() { C c; c.f(); // lvalue, so calls lvalue-reference member f C().f(); // temporary is prvalue, so called rvalue-reference member f move(c).f(); // move changes c to xvalue, so again calls rvalue-reference member f }

Así por ejemplo:

struct C { C operator+(const C& that) const & { C c(*this); // take a copy of this c += that; return c; } C operator+(const C& that) && { (*this) += that; return move(*this); // moving this is ok here } }


En el marco de mi compilador (que se lanzará Sometime Soon ™), usted pasa elementos de información como tokens a un objeto compilador, y luego llama a finalize para indicar el final de la secuencia.

Sería malo destruir un objeto sin tener que finalize llamado, ya que no eliminaría toda su salida. Sin embargo, la finalize no puede ser realizada por el destructor, ya que puede lanzar una excepción, y de la misma manera, es incorrecto pedir que se finalize más si el analizador ya está abortando.

En el caso de que toda la entrada ya esté encapsulada por otro objeto, es bueno pasar la entrada a un objeto compilador de valores.

pile< lexer, parser >( output_handler ).pass( open_file( "source.q" ) );

Sin soporte especial, esto debe ser incorrecto porque no se está llamando a finalize . La interfaz no debería permitir que el usuario haga tal cosa en absoluto.

Lo primero que hay que hacer es descartar el caso en el que nunca se finalize . El ejemplo anterior no está permitido si el prototipo se ajusta con un calificador ref de lvalue como este:

void pass( input_file f ) & { process_the_file(); }

Esto deja espacio para agregar otra sobrecarga que finalice correctamente el objeto. Es rvalue ref-calificado así que se selecciona solo si se llama en un objeto que está expirando.

void pass( input_file f ) && { pass( std::move( f ) ); // dispatch to lvalue case finalize(); }

Ahora, el usuario casi nunca tiene que preocuparse por recordar finalize llamada, ya que la mayoría de los objetos del compilador se crean instancias como temporarios.

Tenga en cuenta que este tipo de cosas no es particular para los miembros calificados por ref. Cualquier función puede tener sobrecargas separadas para t & y t && . La forma en que se pass actualmente se implementa usa el reenvío perfecto y luego las pistas para determinar la semántica correcta:

template< typename compiler, typename arg > void pass( compiler && c, arg a ) { c.take_input( a ); if ( ! std::is_reference< compiler >::value ) { c.finalize(); } }

Hay muchas formas de abordar la sobrecarga. En realidad, las funciones miembro no calificadas son inusuales al no preocuparse por la categoría (lvalue o rvalue) del objeto al que se llama, y ​​no pasar esa información a la función. Cualquier parámetro de función además de lo implícito debe decir algo sobre la categoría de su argumento.