2011-05-26 21 views
14

Sigo siendo un programador novato, sé que la optimización prematura es mala, pero también sé que copiar cosas enormes es malo también.¿Puede el compilador eludir la siguiente copia?

He leído sobre elisión de copia y sus sinónimos, pero los ejemplos en Wikipedia, por ejemplo, me hacen pensar que la elisión de copia solo puede tener lugar si el objeto que se devuelve se devuelve al mismo tiempo que se construye por completo .

¿Qué pasa con objetos como vectores, que generalmente solo tienen sentido cuando se rellenan con algo, cuando se usan como valor de retorno? Después de todo, un vector vacío podría simplemente crearse una instancia manualmente.

Entonces, ¿también funciona en un caso como este?

mal estilo por razones de brevedad:

vector<foo> bar(string baz) 
{ 
    vector<foo> out; 
    for (each letter in baz) 
     out.push_back(someTable[letter]); 

    return out; 
} 

int main() 
{ 
    vector<foo> oof = bar("Hello World"); 
} 

tengo ningún problema real utilizando la barra (vector & cabo, cadena de texto), pero la forma anterior se vería mucho mejor, estéticamente, y de la intención.

+3

Puede ser elidido. Tenga en cuenta sin embargo, que la norma hace _still_ exigir al constructor de copia para que sea accesible (por ejemplo, no privado) – sehe

Respuesta

10

los ejemplos de Wikipedia, por ejemplo, hacen que se parece a mí que copiar elisión sólo puede tener lugar si el objeto a ser devuelto se volvió al mismo tiempo que se construye por completo.

Eso es engañoso (léase: incorrecto). La cuestión es más bien que sólo uno objeto se devuelve en todas las rutas de código, es decir, que sólo uno la construcción del objeto de rendimiento potencial está sucediendo.

Su código está bien, cualquier compilador moderno puede elide la copia.

Por otro lado, el siguiente código podría llegar a generar problemas:

vector<int> foo() { 
    vector<int> a; 
    vector<int> b; 
    // … fill both. 
    bool c; 
    std::cin >> c; 
    if (c) return a; else return b; 
} 

En este caso, el compilador necesita para construir totalmente dos objetos distintos, y sólo tarde decide cuál de ellos se volvió, por lo tanto, tiene que copiar una vez porque no puede construir directamente el objeto devuelto en la ubicación de la memoria de destino.

+0

Su contraejemplo está realmente explícitamente exento de copia elisión por el mismo párrafo que cito en mi respuesta (pero otra parte). La elisión solo está permitida si la expresión en la declaración de devolución es el nombre de un objeto de clase. –

+0

¿puede el compilador decidir _mover_ el vector devuelto? – user396672

+0

@ user396672 No soy firme en C++ 0x, pero la lógica dicta que esto debería ser posible (ya que el original ya no se necesita después del 'return'). –

5

No hay nada que impida que el compilador elimine la copia. Esto se define en 08.12.15:

[...] Esto elision de operaciones de copia se permitido en los circunstancias siguientes (que pueden combinarse para eliminar múltiples copias):

[. ..]

  • cuando un objeto de clase temporal que no ha sido atado a una referencia (12.2) sería copiado a un objeto de clase con el mismo tipo cv-calificado, el c opiar operación puede ser omitido por la construcción del objeto temporal directamente en el blanco de la copia omitido

Si lo hace realmente depende del compilador y las opciones que utiliza.

+0

que se parece a mí como una respuesta general sobre el tema, como en: "Si el compilador eran lo suficientemente inteligente como para detectar oportunidades para eludir la Copiar, lo haría incluso si el objeto que se iba a devolver se editara en toda la función de retorno ", ¿sí? Editar: Ah, ese fragmento es informativo, lástima de mí si estuviera en los artículos que leo ... ¡gracias! – Erius

+0

@Erius: solo depende de dos cosas: si el compilador es lo suficientemente inteligente, y si está permitido hacerlo. Solo puedo responder al primero, ya que no conozco tu compilador y tu configuración. –

+0

Bueno, es el (afortunadamente el último) MSVC uno, optimizaciones completas, pero ahora entiendo completamente la parte sobre la inteligencia del compilador, así que gracias de nuevo. – Erius

5

Ambas copias implícitas del vector pueden (ya menudo son) eliminadas. La optimización del valor de retorno nombrada puede eliminar la copia implícita en la declaración de devolución return out; y está permitida la eliminación temporal del implícito implícito en la inicialización de copia de oof.

Con ambas optimizaciones en juego, el objeto construido en vector<foo> out; es el mismo objeto que oof.

Es más fácil probar cuáles de estas optimizaciones se están realizando con un caso de prueba artificial como este.

struct CopyMe 
{ 
    CopyMe(); 
    CopyMe(const CopyMe& x); 
    CopyMe& operator=(const CopyMe& x); 

    char data[1024]; // give it some bulk 
}; 

void Mutate(CopyMe&); 

CopyMe fn() 
{ 
    CopyMe x; 
    Mutate(x); 
    return x; 
} 

int main() 
{ 
    CopyMe y = fn(); 
    return 0; 
} 

El constructor de copia se declara pero no se define por lo que llama a que no puede ser inline y eliminado. La compilación con un gcc 4.4 relativamente nuevo proporciona el siguiente ensamblado en -O3 -fno-inline (filtrado para exigir los nombres de C++ y editado para eliminar sin código).

fn(): 
     pushq %rbx 
     movq %rdi, %rbx 
     call CopyMe::CopyMe() 
     movq %rbx, %rdi 
     call Mutate(CopyMe&) 
     movq %rbx, %rax 
     popq %rbx 
     ret 

main: 
     subq $1032, %rsp 
     movq %rsp, %rdi 
     call fn() 
     xorl %eax, %eax 
     addq $1032, %rsp 
     ret 

Como se puede ver no hay llamadas al constructor de copia. De hecho, gcc realiza estas optimizaciones incluso en -O0. Debe proporcionar el -fno-elide-constructors para desactivar este comportamiento; si haces esto, entonces gcc genera dos llamadas al constructor de copia de CopyMe, una dentro y otra fuera de la llamada al fn().

fn(): 
     movq %rbx, -16(%rsp) 
     movq %rbp, -8(%rsp) 
     subq $1048, %rsp 
     movq %rdi, %rbx 
     movq %rsp, %rdi 
     call CopyMe::CopyMe() 
     movq %rsp, %rdi 
     call Mutate(CopyMe&) 
     movq %rsp, %rsi 
     movq %rbx, %rdi 
     call CopyMe::CopyMe(CopyMe const&) 
     movq %rbx, %rax 
     movq 1040(%rsp), %rbp 
     movq 1032(%rsp), %rbx 
     addq $1048, %rsp 
     ret 

main: 
     pushq %rbx 
     subq $2048, %rsp 
     movq %rsp, %rdi 
     call fn() 
     leaq 1024(%rsp), %rdi 
     movq %rsp, %rsi 
     call CopyMe::CopyMe(CopyMe const&) 
     xorl %eax, %eax 
     addq $2048, %rsp 
     popq %rbx 
     ret 
Cuestiones relacionadas