Articulo de referencia

Copiar omisión

En la programación informática en C++ , la elisión de copias se refiere a una técnica de optimización del compilador que elimina la copia innecesaria de objetos . El estándar de...

En la programación informática en C++ , la elisión de copias 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, suponiendo que el programa se ejecutara exactamente como lo exige el estándar. Además, 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 (ver más abajo ). Otra optimización ampliamente implementada, descrita en el estándar C++ , es cuando un objeto temporal de tipo de clase se copia a un objeto del mismo tipo. [ 1 ] [ 2 ] Como resultado, la inicialización por copia suele ser equivalente a la inicialización directa en términos de rendimiento, pero no en semántica; la inicialización por copia todavía requiere un constructor de copia accesible . [ 3 ] La optimización no se puede aplicar a un objeto temporal que se haya vinculado a una referencia.

Ejemplo

importar std ;// una variable global mutable int n = 0 ;struct MyStruct { explicit MyStruct ([[ maybe_unused ]] int x ) {} MyStruct ([[ maybe_unused ]] const MyStruct & x ) { ++ n ; // el constructor de copia tiene un efecto secundario visible // modifica un objeto con duración de almacenamiento estático } };int main () { MyStruct c1 ( 42 ); // inicialización directa, llama a MyStruct::MyStruct(int) MyStruct c2 = MyStruct ( 42 ); // inicialización por copia, llama a MyStruct::MyStruct(const MyStruct&)std :: println ( "{}" , n ); // imprime 0 si se omitió la copia, 1 en caso contrario }

Según el estándar, se puede aplicar una optimización similar a los objetos que se lanzan y se capturan , [ 4 ] [ 5 ] 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 del objeto de excepción al objeto declarado en la declaración de excepción de la cláusula catch . Tampoco está claro si esta optimización se aplica solo a objetos temporales o también a objetos con nombre. [ 6 ] Dado el siguiente código fuente:

importar std ;struct MyStruct { MyStruct () = default ; MyStruct ([[ maybe_unused ]] const MyStruct & x ) { std :: println ( "¡Hola Mundo!" ); } };void f () { MyStruct c ; throw c ; // Copiando el objeto con nombre c en el objeto de excepción. // No está claro si esta copia puede omitirse. }int main () { try { f (); } catch ( MyStruct c ) { // Copiando el objeto de excepción en la variable temporal en la // declaración de excepción. // Tampoco está claro si esta copia puede omitirse. } }

Por lo tanto, un compilador conforme debería producir un programa que imprima "¡Hola Mundo!" dos veces. En la revisión C++11 del estándar C++, se han abordado estos problemas, permitiendo esencialmente que se omitan tanto la copia del objeto con nombre al objeto de excepción como la copia al objeto declarado en el manejador de excepciones. [ 6 ]

GCC ofrece la -fno-elide-constructorsopción de deshabilitar la eliminación de copias. Esta opción es útil para observar (o no observar) los efectos de la optimización del valor de retorno u otras optimizaciones en las que se eliminan copias. En general, no se recomienda deshabilitar esta importante optimización.

C++17 proporciona "eliminación de copia garantizada", un prvalue no se materializa hasta que se necesita, y luego se construye directamente en el almacenamiento de su destino final. [ 7 ]

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 la eliminación del objeto temporal creado para almacenar el valor de retorno de una función . [ 8 ] El estándar de C++ permite que la RVO cambie el comportamiento observable del programa resultante . [ 9 ]

Resumen

En general, el estándar C++ permite que un compilador realice cualquier optimización, siempre que el ejecutable resultante exhiba el mismo comportamiento observable como si (es decir, simulando) que se han cumplido todos los requisitos del estándar. Esto se conoce comúnmente como la " regla como si ". [ 10 ] [ 2 ] 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 instrucción return , incluso si el constructor de copia tiene efectos secundarios . [ 1 ] [ 2 ]

El siguiente ejemplo demuestra un escenario en el que la implementación puede eliminar una o ambas copias que se realizan, incluso si el constructor de copia tiene un efecto secundario visible (imprimir texto). [ 1 ] [ 2 ] La primera copia que puede eliminarse es aquella en la que un objeto temporal sin nombre MyStructpodría copiarse en el valor de retornof de la función . La segunda copia que puede eliminarse es la copia del objeto temporal devuelto por a .fobj

importar std ;struct MyStruct { MyStruct () = default ; MyStruct ([[ maybe_unused ]] const MyStruct & x ) { std :: println ( "Se hizo una copia." ); } };MyStruct f () { return MyStruct (); }int main () { std :: println ( "Hola Mundo!" ); MyStruct obj = f (); }

Dependiendo del compilador y de su configuración, el programa resultante puede mostrar cualquiera de las siguientes salidas:

¡Hola Mundo! Se hizo una copia. Se hizo una copia.
¡Hola Mundo! Se hizo una copia.
¡Hola Mundo!

Fondo

Devolver un objeto de tipo integrado desde una función generalmente conlleva poca o ninguna sobrecarga, ya que el objeto normalmente cabe en un registro de la CPU . Devolver un objeto más grande de tipo clase puede requerir una copia más costosa de una ubicación de memoria a otra. Para evitar esto, una implementación puede crear un objeto oculto en el marco de pila del llamador y pasar la dirección de este objeto a la función. El valor de retorno de la función se copia entonces en el objeto oculto. [ 11 ] Por lo tanto, código como este:

struct Data { char bytes [ 16 ]; };Datos f () { Datos resultado ; // generar resultado devolver resultado ; }int main () { Data d = f (); }

puede generar código equivalente a este:

struct Data { char bytes [ 16 ]; };Datos * f ( Datos * _hiddenAddress ) { Datos resultado ; // copiar el resultado en el objeto oculto * _hiddenAddress = resultado ; return _hiddenAddress ; }int main () { Data _hidden ; // crea un objeto oculto Data d = * f ( &_ hidden ); // copia el resultado en d }

lo que provoca que el Dataobjeto se copie dos veces.

En las primeras etapas de la evolución de C++ , la incapacidad del lenguaje para devolver eficientemente un objeto de tipo clase desde una función se consideraba una debilidad. [ 12 ] Alrededor de 1991, Walter Bright implementó una técnica para minimizar la copia, reemplazando efectivamente el objeto oculto y el objeto con nombre dentro de la función con el objeto utilizado para contener el resultado: [ 13 ]

struct Data { char bytes [ 16 ]; };void f ( Data * p ) { // genera el resultado directamente en *p }int main () { Data d ; f ( & d ); }

Bright implementó esta optimización en su compilador Zortech C++ . [ 12 ] Esta técnica en particular fue posteriormente denominada "Optimización del valor de retorno con nombre" (NRVO), en referencia al hecho de que se omite la copia de un objeto con nombre. [ 13 ]

Soporte del compilador

La optimización del valor de retorno es compatible con la mayoría de los compiladores. [ 8 ] [ 14 ] [ 15 ] 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 objetos con nombres diferentes dependiendo de la ruta de ejecución: [ 11 ] [ 14 ] [ 16 ]

importar std ;usando std :: string ;string f ( bool cond = false ) { string first = "first" ; string second = "second" ; // La función puede devolver uno de dos objetos con nombre // dependiendo de su argumento. Es posible que no se aplique RVO . return cond ? first : second ; }int main () { string resultado = f (); }
  • Copiar elisión en cppreference.com

Referencias

  1. 1 2 3 ISO / IEC (2003). ISO/IEC 14882:2003(E): Lenguajes de programación - C++ §12.8 Copia de objetos de clase [class.copy] párr. 15
  2. 1 2 3 4 ISO / IEC (2003). "§ 12.8 Copia de objetos de clase [class.copy]". ISO/IEC 14882:2003(E): Lenguajes de programación - C++ (PDF) . párr. 15. Archivado del original (PDF) el 10-04-2023 . Recuperado el 26-02-2024 .
  3. Sutter, Herb (2001). More Exceptional C++ . Addison-Wesley.
  4. ISO / IEC (2003). ISO/IEC 14882:2003(E): Lenguajes de programación - C++ §15.1 Lanzar una excepción [except.throw] párr. 5
  5. ISO / IEC (2003). ISO/IEC 14882:2003(E): Lenguajes de programación - C++ §15.3 Manejo de excepciones [except.handle] párr. 17
  6. 1 2 "Informes de defectos del lenguaje principal estándar de C++" . WG21 . Consultado el 27 de marzo de 2009 .
  7. "Copiar elisión - cppreference.com" . en.cppreference.com .
  8. 1 2 Meyers, Scott (1995). More Effective C++ . Addison-Wesley. ISBN 9780201633719.
  9. ^ Alexandrescu, Andrei (1 de febrero de 2003). "Mover constructores" . Diario del Dr. Dobb . Consultado el 25 de marzo de 2009 .
  10. ISO / IEC (2003). ISO/IEC 14882:2003(E): Lenguajes de programación - C++ §1.9 Ejecución del programa [introducción a la ejecución] párr. 1
  11. ^ Bulka , Dov; David Mayhew (2000). C++ eficiente . Addison-Wesley. ISBN 0-201-37950-3.
  12. 1 2 Lippman, Stan (2004-02-03). "Optimización del valor de retorno del nombre" . Microsoft . Recuperado el 23 de marzo de 2009 .
  13. 1 2 "Glosario del lenguaje de programación D 2.0" . Digital Mars . Consultado el 23 de marzo de 2009 .
  14. 1 2 Shoukry, Ayman B. (octubre de 2005). "Optimización del valor de retorno con nombre en Visual C++ 2005" . Microsoft . Recuperado el 20 de marzo de 2009 .
  15. "Opciones que controlan el dialecto de C++" . GCC . 17 de marzo de 2001. Consultado el 20 de enero de 2018 .
  16. Hinnant, Howard; et al. (10 de septiembre de 2002). "N1377: Una propuesta para agregar soporte de semántica de movimiento al lenguaje C++" . WG21 . Recuperado el 25 de marzo de 2009 .