2012-07-15 12 views
9

Tengo curiosidad sobre cómo funciona exactamente esta característica. Considere algo así como¿Cómo sabe el compilador mover las variables locales?

std::unique_ptr<int> f() { std::unique_ptr<int> lval(nullptr); return lval; } 

Este código se compila bien, incluso para un tipo de movimiento de sólo, como el compilador implícitamente lo mueve. Pero, lógicamente, para cualquier expresión de retorno, determinar si el resultado se refiere o no a una variable local resolvería el problema de detención, y si el compilador simplemente tratara todas las variables locales como valores en la expresión de retorno, entonces esto sería problemático ya que la variable se puede hacer referencia en esa expresión una vez. Incluso si un local solo tuviera una referencia directa, no podría probar que no tenía otros alias indirectos.

Entonces, ¿cómo sabe el compilador cuándo moverse desde la expresión de retorno?

Respuesta

10

Existe una regla simple: si se cumplen las condiciones para la elisión de copia (excepto que la variable puede ser un parámetro de función), se trata como valor de r. Si eso falla, trátelo como lvalue. De lo contrario, tratar como lvalue.

§12.8 [class.copy] p32

Cuando se cumplen los criterios para la elisión de una operación de copia o se cumplirían, salvo por el hecho de que el objeto de origen es un parámetro de función, y el objeto que se va a copiar se designa por una lvalue, la resolución de sobrecarga para seleccionar el constructor de la copia se realiza primero como si el objeto se designara con un valor r. Si la resolución de sobrecarga falla, o si el tipo del primer parámetro del constructor seleccionado no es una referencia rvalue al tipo del objeto (posiblemente cv-qualified), la resolución de sobrecarga se realiza nuevamente, considerando el objeto como un valor l. [Nota: Esta resolución de sobrecarga de dos etapas debe realizarse independientemente de si se producirá elisión de copia. Determina el nombre del constructor que se invocará si no se realiza una elisión, y el constructor seleccionado debe estar accesible incluso si se elimina la llamada. -fin nota]

Ejemplo:

template<class T> 
T f(T v, bool b){ 
    T t; 
    if(b) 
    return t; // automatic move 
    return v; // automatic move, even though it's a parameter 
} 

No es que estoy muy de acuerdo con esa regla, ya que no hay ningún movimiento automático en el siguiente código:

template<class T> 
struct X{ 
    T v; 
}; 

template<class T> 
T f(){ 
    X<T> x; 
    return x.v; // no automatic move, needs 'std::move' 
} 

Véase también this question of mine.

+0

+1 alta velocidad de respuesta ... ¿Qué es exactamente –

+0

"* save * por el hecho de que el objeto fuente es un parámetro de función" mean? – fredoverflow

+0

@FredOverflow: "Si se cumplen las condiciones para la elisión de copia (excepto que la variable puede ser un parámetro de función)" es lo que significa. La elisión de copia no se aplica a los parámetros de la función, el movimiento automático sí lo hace. – Xeo

5

El estándar no dice "se refiere a una variable local", pero dice "es el nombre de un objeto automático no volátil".

§12.8 [class.copy] p31

[...] Esta elisión de las operaciones de copiar/mover, llamado copia elisión, está permitido en las siguientes circunstancias [...]:

  • en una return instrucción en una función con un tipo de devolución de clase, cuando la expresión es el nombre de un objeto automático no volátil [...]

Por lo tanto, no se permiten los punteros ni las referencias. Si lo lee en malos caminos que buscan explotar una interpretación que es menos probable que se han previsto, se puede decir que significa mover el siguiente

struct A { 
    std::unique_ptr<int> x; 
    std::unique_ptr<int> f() { return x; } 
}; 

int main() { A a; a.f(); } 

En este caso, la expresión de retorno es el nombre de una variable con duración de almacenamiento automático. Algunos otros párrafos en el Estándar se pueden interpretar de múltiples maneras, pero la regla es tomar la interpretación que es más probable que se pretenda.

2

Creo que sobreestima la capacidad del compilador aquí.

Si devuelve directamente una variable local, entonces el trabajo es fácil: puede moverlo.

Si está llamando a una expresión que requiere pasar de una variable local, debe especificarla manualmente.

Ver some examples here.

3

Pero, lógicamente, para cualquier expresión de retorno, determinar si el resultado se refiere o no a una variable local resolvería el problema de detención.

Estás sobreestimando la complejidad del problema. Claramente, el compilador ve si la expresión de retorno menciona una de las variables que es local, y debe hacer un seguimiento de todas esas variables para llamar a los destructores de todos modos. Tenga en cuenta que se moverá sólo si el retorno menciona explícitamente la variable, si devuelve un puntero o una referencia a una variable local no tiene por qué hacerlo:

std::unique_ptr<int>& same(std::unique_ptr<int>& x) { return x; } 
std::unique_ptr<int> foo() { 
    std::unique_ptr<int> p(new int); 
    std::unique_ptr<int>& r = same(p); 
    return r;       // FAIL 
} 
Cuestiones relacionadas