En la programación de computadoras C ++ , la elisión de copia se refiere a una técnica de optimización del compilador que elimina la copia innecesaria de objetos . El estándar del lenguaje C ++ generalmente permite que las implementaciones realicen cualquier optimización, siempre que el comportamiento observable del programa resultante sea el mismo que si , es decir, fingiendo, el programa se ejecutara exactamente como lo exige el estándar.
El estándar también describe algunas situaciones en las que se puede eliminar la copia incluso si esto alteraría el comportamiento del programa, siendo la más común la optimización del valor de retorno. Otra optimización ampliamente implementada, descrita en el estándar C ++ , es cuando un objeto temporal del tipo de clase se copia en un objeto del mismo tipo. [1] Como resultado, la inicialización de copia suele ser equivalente a la inicialización directa en términos de rendimiento, pero no en semántica; La inicialización de copia aún requiere un constructor de copia accesible . [2] La optimización no se puede aplicar a un objeto temporal que se ha vinculado a una referencia.
Ejemplo
#include int n = 0 ;struct C { explícito C ( int ) {} C ( const C & ) { ++ n ; } // el constructor de la copia tiene un efecto secundario visible }; // modifica un objeto con una duración de almacenamiento estáticaint main () { C c1 ( 42 ); // inicialización directa, llama a C :: C (int) C c2 = C ( 42 ); // copia-inicialización, llama a C :: C (const C &) std :: cout << n << std :: endl ; // imprime 0 si la copia se elidió, 1 en caso contrario }
De acuerdo con el estándar, se puede aplicar una optimización similar a los objetos que se lanzan y capturan , [3] [4] pero no está claro si la optimización se aplica tanto a la copia del objeto lanzado al objeto de excepción como a la copia de la excepción. objeto al objeto declarado en la declaración de excepción de la cláusula catch . Tampoco está claro si esta optimización solo se aplica a objetos temporales o también a objetos con nombre. [5] Dado el siguiente código fuente:
#include struct C { C () = predeterminado ; C ( const C & ) { std :: cout << "¡Hola mundo! \ N " ; } };vacío f () { C c ; tirar c ; // copiando el objeto c nombrado en el objeto de excepción. } // No está claro si esta copia se puede elidir (omitir).int main () { prueba { f (); } catch ( C c ) { // copiando el objeto de excepción en el temporal en la // declaración de excepción. } // Tampoco está claro si esta copia se puede elidir (omitir). }
Por lo tanto, un compilador conforme debería producir un programa que imprima "¡Hola mundo!" dos veces. En la revisión actual del estándar C ++ ( C ++ 11 ), los problemas se han abordado, esencialmente permitiendo que se eliminen tanto la copia del objeto nombrado al objeto de excepción como la copia al objeto declarado en el controlador de excepciones. [5]
GCC ofrece la -fno-elide-constructors
opción de deshabilitar la elisión de copia. Esta opción es útil para observar (o no observar) los efectos de la optimización del valor de retorno u otras optimizaciones donde se eliden las copias. Por lo general, no se recomienda deshabilitar esta importante optimización.
Optimización del valor de retorno
En el contexto del lenguaje de programación C ++ , la optimización del valor de retorno ( RVO ) es una optimización del compilador que implica eliminar el objeto temporal creado para contener el valor de retorno de una función . [6] RVO puede cambiar el comportamiento observable del programa resultante por el estándar C ++ . [7]
Resumen
En general, el estándar C ++ permite que un compilador realice cualquier optimización, siempre que el ejecutable resultante muestre el mismo comportamiento observable como si (es decir, fingiendo) se hubieran cumplido todos los requisitos del estándar. Esto se conoce comúnmente como la " regla como si ". [8] El término optimización del valor de retorno se refiere a una cláusula especial en el estándar C ++ que va incluso más allá de la regla "como si": una implementación puede omitir una operación de copia resultante de una declaración de retorno , incluso si el constructor de copia tiene side efectos . [1]
El siguiente ejemplo demuestra un escenario en el que la implementación puede eliminar una o ambas copias que se están realizando, incluso si el constructor de copias tiene un efecto secundario visible (impresión de texto). [1] La primera copia que puede ser eliminado es aquel en el que un anónimo temporal C
podría ser copiado en la función f
's valor de retorno . La segunda copia que puede eliminarse es la copia del objeto temporal devuelto por f
a obj
.
#include struct C { C () = predeterminado ; C ( const C & ) { std :: cout << "Se hizo una copia. \ N " ; } };C f () { return C (); }int main () { std :: cout << "¡Hola mundo! \ n " ; C obj = f (); }
Dependiendo del compilador y la configuración de ese compilador, el programa resultante puede mostrar cualquiera de los siguientes resultados:
¡Hola Mundo!Se hizo una copia.Se hizo una copia.
¡Hola Mundo!Se hizo una copia.
¡Hola Mundo!
Fondo
Devolver un objeto de tipo incorporado desde una función generalmente conlleva poca o ninguna sobrecarga, ya que el objeto generalmente cabe en un registro de CPU . La devolución de un objeto más grande de tipo de clase puede requerir una copia más cara de una ubicación de memoria a otra. Para evitar esto, una implementación puede crear un objeto oculto en el marco de pila de la persona que llama y pasar la dirección de este objeto a la función. Luego, el valor de retorno de la función se copia en el objeto oculto. [9] Por lo tanto, un código como este:
struct Data { char bytes [ 16 ]; };Datos F () { Resultado de datos = {}; // generar resultado return result ; } int main () { Datos d = F (); }
puede generar código equivalente a esto:
struct Data { char bytes [ 16 ]; };Datos * F ( Datos * _hiddenAddress ) { Resultado de datos = {}; // copiar el resultado en un objeto oculto * _hiddenAddress = result ; return _hiddenAddress ; } int main () { Datos _hidden ; // crear datos de objetos ocultos d = * F ( & _hidden ); // copia el resultado en d }
lo que hace que el Data
objeto se copie dos veces.
En las primeras etapas de la evolución de C ++ , la incapacidad del lenguaje para devolver de manera eficiente un objeto de tipo de clase desde una función se consideró una debilidad. [10] Alrededor de 1991, Walter Bright implementó una técnica para minimizar la copia, reemplazando efectivamente el objeto oculto y el objeto nombrado dentro de la función con el objeto utilizado para mantener el resultado: [11]
struct Data { char bytes [ 16 ]; };void F ( Data * p ) { // generar el resultado directamente en * p }int main () { Datos d ; F ( & d ); }
Bright implementó esta optimización en su compilador Zortech C ++ . [10] Esta técnica en particular se acuñó más tarde "Optimización del valor de retorno con nombre", refiriéndose al hecho de que la copia de un objeto con nombre se elide. [11]
Soporte del compilador
La optimización del valor de retorno es compatible con la mayoría de los compiladores. [6] [12] [13] Sin embargo, puede haber circunstancias en las que el compilador no pueda realizar la optimización. Un caso común es cuando una función puede devolver diferentes objetos con nombre dependiendo de la ruta de ejecución: [9] [12] [14]
#include std :: string F ( bool cond = false ) { std :: string primero ( "primero" ); std :: string second ( "segundo" ); // la función puede devolver uno de dos objetos nombrados // dependiendo de su argumento. ¿Es posible que no se aplique RVO return cond ? primero : segundo ; }int main () { estándar :: resultado de cadena = F (); }
enlaces externos
- Copiar elision en cppreference.com
Referencias
- ^ a b c ISO / IEC (2003). ISO / IEC 14882: 2003 (E): Lenguajes de programación - C ++ §12.8 Copiar objetos de clase [class.copy] párr. 15
- ^ Sutter, Hierba (2001). C ++ más excepcional . Addison-Wesley.
- ^ ISO / IEC (2003). ISO / IEC 14882: 2003 (E): Lenguajes de programación - C ++ §15.1 Lanzamiento de una excepción [except.throw] párr. 5
- ^ ISO / IEC (2003). ISO / IEC 14882: 2003 (E): Lenguajes de programación - C ++ §15.3 Manejo de una excepción [except.handle] párr. 17
- ^ a b "Informes de defectos del lenguaje básico estándar de C ++" . WG21 . Consultado el 27 de marzo de 2009 .
- ^ a b Meyers, Scott (1995). C ++ más eficaz . Addison-Wesley.
- ^ Alexandrescu, Andrei (1 de febrero de 2003). "Mover constructores" . Diario del Dr. Dobb . Consultado el 25 de marzo de 2009 .
- ^ ISO / IEC (2003). ISO / IEC 14882: 2003 (E): Lenguajes de programación - C ++ §1.9 Ejecución del programa [intro.execution] párr. 1
- ^ a b Bulka, Dov; David Mayhew (2000). C ++ eficiente . Addison-Wesley. ISBN 0-201-37950-3.
- ^ a b Lippman, Stan (3 de febrero de 2004). "Optimización del valor de retorno de nombre" . Microsoft . Consultado el 23 de marzo de 2009 .
- ^ a b "Glosario D Lenguaje de programación 2.0" . Marte digital . Consultado el 23 de marzo de 2009 .
- ^ a b Shoukry, Ayman B. (octubre de 2005). "Optimización del valor devuelto con nombre en Visual C ++ 2005" . Microsoft . Consultado el 20 de marzo de 2009 .
- ^ "Opciones que controlan el dialecto de C ++" . GCC . 2001-03-17 . Consultado el 20 de enero de 2018 .
- ^ Hinnant, Howard; et al. (10 de septiembre de 2002). "N1377: una propuesta para agregar soporte de semántica de movimiento al lenguaje C ++" . WG21 . Consultado el 25 de marzo de 2009 .