2012-05-31 8 views
14

que tienen una clase simple:¿Debo escribir constructores usando rvalues ​​para std :: string?

class X 
{ 
    std::string S; 
    X (const std::string& s) : S(s) { } 
}; 

He leído un poco sobre rvalues ​​últimamente, y me he estado preguntando si debería escribir constructor para X usando rvalue, por lo que no sería capaz de detectar temporal objetos del tipo std::string?

creo que debe ser algo como:

X (std::string&& s) : S(s) { } 

En cuanto a mi conocimiento, la implementación de std :: string en los compiladores de C++ de apoyo 11 debe utilizar que el movimiento de constructor cuando esté disponible.

+2

[Pertinente] (http://stackoverflow.com/questions/7592630/is-pass-by-value-a-reasonable-default-in-c11). –

+0

El código entre comillas es un patrón discontinuo que abarca lo peor de ambos mundos: limita a la persona que realiza la llamada a pasar un valor r a 'X()', pero luego no reenvía la validez al constructor para 'S', que por lo tanto no puede moverse y debe copiar. Por lo tanto, impone una carga a los usuarios de la función, al tiempo que les impide obtener ningún beneficio compensatorio a cambio. –

+0

@underscore_d Creo que cinco años después llegamos a un acuerdo en las respuestas de que mi código no era perfecto;) –

Respuesta

23
X (std::string&& s) : S(s) { } 

Eso no es un constructor teniendo una rvalue, pero un constructor teniendo una rvalue referencia. No debe tomar referencias de valores en este caso. En vez paso por valor y luego mover en el miembro:

X (std::string s) : S(std::move(s)) { } 

La regla de oro es que si necesita copiar, lo hacen en la interfaz.

+3

¿Por qué no debería tener un constructor que recibe una referencia-valor-real en este caso? – ncasas

+2

@ncasas: explosión combinatoria? –

+6

@ncasas: Entonces necesitaría proporcionar un 'X (std :: string &&)' y 'X (std :: string const &)' (para el caso donde el argumento utilizado para construir el objeto es un * lvalue *) Las alternativas son manejadas fácilmente por el compilador en el constructor * pass-by-value *, el parámetro usará el constructor existente eligiendo de 'std :: string (std :: string const &)' o 'std :: string (std :: cadena &&) 'para crear la copia. Es decir, proporcionar solo el constructor de valores por defecto permite que el compilador use las funciones de la biblioteca en lugar de tener que reescribirlas. –

13

No, no deberías. Lo que debe hacer es reemplazar el constructor actual con uno como este:

X (std::string s) :S(std::move(s)) {} 

Ahora usted puede manejar ambos valores L, que se copian en el parámetro luego se trasladó en su clase cadena, y los valores de r, que se moverá dos veces (su compilador puede optimizar este trabajo extra).

En su mayor parte (hay excepciones en las que no entraré aquí), no debería escribir funciones que tomen referencias de valor r, excepto los constructores de movimiento de las clases que escribe. Cada vez que necesite su propia copia de un valor, y esto no se aplica solo a los constructores, debe incluirlo por valor y moverlo donde deba ir. Deje que el propio constructor de movimientos de la clase decida si el valor debe copiarse o moverse en función de si recibe un valor r o un valor l. Después de todo, se introdujeron referencias de valor r para hacer nuestras vidas más fáciles, no más difíciles.

+0

Precisamente me preguntaba si las referencias rvalue tenían otros usos además de los constructores de movimientos (y obviamente las funciones 'move' y' forward' proporcionadas por la biblioteca estándar). ¿Conoces otros casos en los que se deben usar referencias de valores-r? –

+0

@LucTouraille: en funciones de plantilla para permitir el reenvío perfecto. p.ej. 'emplace_back',' make_shared' –

+0

¡Oh, claro, por supuesto! Evoqué 'adelante' y ni siquiera pensé en su propósito ... –

5

Como necesita una copia del argumento, tome el parámetro por valor. Luego, muévelo a sus datos de miembro. Es el constructor std::string que es responsable de detectar si el argumento dado es un valor r o un valor l, no usted.

class X 
{ 
    std::string s_; 
    X(std::string s) : s_(std::move(s)) {} 
}; 
19

Con el interés de aclarar: Las respuestas de valor por paso no son incorrectas. Pero tampoco es su primera suposición de la adición de una sobrecarga de string&&, salvo por un detalle:

Añadir:

X (std::string&& s) : S(std::move(s)) { } 

es decir, usted todavía necesita la move porque aunque s tiene un tipo declarado de referencia rvalue a string, la expresión s utilizado para inicializar S es una expresión de tipo lvalue string.

De hecho, la solución que primero propuso (con el movimiento agregado) es ligeramente más rápida que la solución de valor por paso. Pero ambos son correctos. La solución de paso por valor llama al constructor de movimiento de cadena un tiempo adicional al pasar argumentos lvalue y xvalue al constructor de X. En el caso de argumentos lvalue, una copia se hace de todos modos son aproximadamente la misma velocidad).

En el caso de los argumentos xvalue (un valor x es un lvalue que se ha pasado a std::move), la solución de paso por valor requiere dos construcciones de movimiento en lugar de una. Por lo tanto, es dos veces más caro que la solución de referencia pass by rvalue. Pero aún así muy rápido.

El objetivo de esta publicación es dejar en claro: pasar por valor es una solución aceptable. Pero no es la única solución aceptable. La sobrecarga con pass-by-rvalue-ref es más rápida, pero tiene la desventaja de que el número de sobrecargas requiere escalas como 2^N a medida que aumenta el número de argumentos N.

Cuestiones relacionadas