stringtranslate.com

Copiar elisión

En la programación informática 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, pretendiendo que el programa se ejecutara exactamente como lo exige el estándar. Más allá de eso, 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 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 todavía requiere un constructor de copia accesible . [3] La optimización no se puede aplicar a un objeto temporal que se ha vinculado a una referencia.

Ejemplo

#include <flujo de datos> int n = 0 ;   struct C { explicit C ( int ) {} C ( const C & ) { ++ n ; } // el constructor de copia tiene un efecto secundario visible }; // modifica un objeto con duración de almacenamiento estático            int main () { C c1 ( 42 ); // inicialización directa, llama a C::C(int) C c2 = C ( 42 ); // inicialización de copia, llama a C::C(const C&)           std :: cout << n << std :: endl ; // imprime 0 si se eliminó 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 solo se aplica a los objetos temporales o también a los objetos nombrados. [6] Dado el siguiente código fuente:

#include <flujo de datos> struct C { C () = predeterminado ; C ( const C & ) { std :: cout << "¡Hola mundo! \n " ; } };            void f () { C c ; throw c ; // copiando el objeto nombrado c en el objeto de excepción. } // No está claro si esta copia puede elidirse (omitirse).        int main () { try { 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 puede elidirse (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 los problemas, permitiendo esencialmente que se omita tanto la copia del objeto nombrado 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 desactivar la eliminación de copias. Esta opción es útil para observar (o no) los efectos de la optimización del valor de retorno u otras optimizaciones en las que se eliminan copias. Por lo general, no se recomienda desactivar esta importante optimización.

C++17 proporciona "elisió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 eliminar el objeto temporal creado para contener el valor de retorno de una función . [8] El estándar C++ permite que 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 muestre el mismo comportamiento observable como si (es decir, pretendiendo) se hubieran 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 declaración de retorno , 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 están realizando, incluso si el constructor de copias tiene un efecto secundario visible (imprimir texto). [1] [2] La primera copia que se puede eliminar es aquella en la que Cse puede copiar un temporal sin nombre en el valor de retornof de la función . La segunda copia que se puede eliminar es la copia del objeto temporal devuelto por to .fobj

#include <flujo de datos> struct C { C () = default ; C ( const C & ) { std :: cout << "Se realizó una copia. \n " ; } };            C f () { devolver C (); }    int main () { std :: cout << "¡Hola mundo! \n " ; C obj = f (); }         

Dependiendo del compilador y de la configuración de éste, 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 incorporado desde una función normalmente conlleva poca o ninguna sobrecarga, ya que el objeto normalmente cabe en un registro de CPU . Devolver un objeto más grande de tipo de 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 luego en el objeto oculto. [11] Por lo tanto, código como este:

estructura Datos { char bytes [ 16 ]; };      Datos F () { Datos resultado = {}; // generar resultado devolver resultado ; }         int main () { Datos d = F (); }      

Puede generar un código equivalente a este:

estructura Datos { char bytes [ 16 ]; };    Datos * F ( Datos * _hiddenAddress ) { Datos resultado = {}; // copiar el resultado en el objeto oculto * _hiddenAddress = resultado ; devolver _hiddenAddress ; }             int main () { Data _hidden ; // crear objeto oculto Data d = * F ( & _hidden ); // copiar el resultado en d }          

lo que hace 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 nombrado dentro de la función con el objeto utilizado para almacenar el resultado: [13]

estructura Datos { char bytes [ 16 ]; };    void F ( Data * p ) { // generar resultado directamente en *p }    int main () { Datos d ; F ( & d ); }     

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

Compatibilidad con compiladores

La mayoría de los compiladores admiten la optimización del valor de retorno. [8] [14] [15] Sin embargo, pueden existir 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 según la ruta de ejecución: [11] [14] [16]

#include <string> std :: string F ( bool cond = false ) { std :: string first ( "first" ); std :: string second ( "second" ); // la función puede devolver uno de dos objetos nombrados // dependiendo de su argumento. Es posible que no se aplique RVO return cond ? first : second ; }                  int main () { std :: string resultado = F (); }      

Enlaces externos

Referencias

  1. ^ abc ISO / IEC (2003). ISO/IEC 14882:2003(E): Lenguajes de programación - C++ §12.8 Copia de objetos de clase [class.copy] párrafo 15
  2. ^ abcd ISO / IEC (2003). «§ 12.8 Copia de objetos de clase [class.copy]». ISO/IEC 14882:2003(E): Lenguajes de programación - C++ (PDF) . párrafo 15. Archivado desde el original (PDF) el 2023-04-10 . Consultado el 2024-02-26 .
  3. ^ Sutter, Herb (2001). C++ más excepcional . 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árrafo 5
  5. ^ ISO / IEC (2003). ISO/IEC 14882:2003(E): Lenguajes de programación - C++ §15.3 Manejo de una excepción [except.handle] párrafo 17
  6. ^ ab "Informes de defectos del lenguaje básico estándar C++". WG21 . Consultado el 27 de marzo de 2009 .
  7. ^ https://en.cppreference.com/w/cpp/language/copy_elision [ URL desnuda ]
  8. ^ de Meyers, Scott (1995). Un C++ más eficaz . 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 [intro.ejecución] párrafo 1
  11. ^ de Bulka, Dov; David Mayhew (2000). C++ eficiente . Addison-Wesley. ISBN 0-201-37950-3.
  12. ^ ab Lippman, Stan (3 de febrero de 2004). "Optimización del valor de retorno de nombres". Microsoft . Consultado el 23 de marzo de 2009 .
  13. ^ ab "Glosario del lenguaje de programación D 2.0". Digital Mars . Consultado el 23 de marzo de 2009 .
  14. ^ ab Shoukry, Ayman B. (octubre de 2005). "Optimización de valores de retorno con nombre en Visual C++ 2005". Microsoft . Consultado el 20 de marzo de 2009 .
  15. ^ "Opciones que controlan el dialecto de C++". GCC . 2001-03-17 . Consultado el 2018-01-20 .
  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 . Consultado el 25 de marzo de 2009 .