¿Qué son la elisión de copia y la optimización del valor de retorno?

13 minutos de lectura

¿Que son la elision de copia y la optimizacion del
Luciano Grigore

¿Qué es la elisión de copia? ¿Qué es la optimización del valor de retorno (llamado)? ¿Qué implican?

¿En qué situaciones pueden ocurrir? ¿Qué son las limitaciones?

  • Si te hicieron referencia a esta pregunta, probablemente estés buscando la introducción.
  • Para obtener una descripción técnica, consulte la referencia estándar.
  • Ver casos comunes aquí.

  • La elisión de copia es una forma de verlo; la elisión de objetos o la fusión (o confusión) de objetos es otro punto de vista.

    – chico curioso

    25 de agosto de 2015 a las 23:58

  • encontré esto Enlace servicial.

    – buscador sutil

    2 de marzo de 2020 a las 8:30

¿Que son la elision de copia y la optimizacion del
Luciano Grigore

Introducción

Para obtener una descripción técnica, salte a esta respuesta.

Para los casos comunes en los que se produce la elisión de copia, salte a esta respuesta.

La elisión de copia es una optimización implementada por la mayoría de los compiladores para evitar copias adicionales (potencialmente costosas) en ciertas situaciones. Hace factible en la práctica la devolución por valor o la transferencia por valor (se aplican restricciones).

Es la única forma de optimización que elude (¡ja!) la regla del “como si”: la elisión de copia se puede aplicar incluso si copiar/mover el objeto tiene efectos secundarios.

El siguiente ejemplo tomado de Wikipedia:

struct C {
  C() {}
  C(const C&) { std::cout << "A copy was made.\n"; }
};
 
C f() {
  return C();
}
 
int main() {
  std::cout << "Hello World!\n";
  C obj = f();
}

Dependiendo del compilador y la configuración, las siguientes salidas todos son validos:

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


Hola Mundo!
Se hizo una copia.


Hola Mundo!

Esto también significa que se pueden crear menos objetos, por lo que tampoco puede confiar en que se llame a una cantidad específica de destructores. No debe tener una lógica crítica dentro de los constructores o destructores de copiar/mover, ya que no puede confiar en que se los llame.

Si se elide una llamada a un constructor de copia o movimiento, ese constructor aún debe existir y debe ser accesible. Esto asegura que la elisión de copia no permita copiar objetos que normalmente no son copiables, por ejemplo, porque tienen un constructor de copiar/mover privado o eliminado.

C++17: A partir de C++17, Copy Elision está garantizado cuando un objeto se devuelve directamente:

struct C {
  C() {}
  C(const C&) { std::cout << "A copy was made.\n"; }
};
 
C f() {
  return C(); //Definitely performs copy elision
}
C g() {
    C c;
    return c; //Maybe performs copy elision
}
 
int main() {
  std::cout << "Hello World!\n";
  C obj = f(); //Copy constructor isn't called
}

  • ¿Podría explicar cuándo ocurre la segunda salida y cuándo la tercera?

    – zhangxaochen

    19/06/2014 a las 14:00


  • @zhangxaochen cuándo y cómo el compilador decide optimizar de esa manera.

    – Lucian Grigore

    19 de junio de 2014 a las 15:11

  • @zhangxaochen, primera salida: la copia 1 es del retorno a una temperatura y la copia 2 de la temperatura a obj; El segundo es cuando se optimiza uno de los anteriores, probablemente se elide la copia reutnr; el thris ambos son elididos

    – Víctor

    7 de noviembre de 2014 a las 16:06

  • Hmm, pero en mi opinión, esta DEBE ser una característica en la que podamos confiar. Porque si no podemos, afectaría severamente la forma en que implementamos nuestras funciones en C++ moderno (RVO vs std::move). Mientras miraba algunos de los videos de CppCon 2014, realmente tuve la impresión de que todos los compiladores modernos siempre usan RVO. Además, he leído en alguna parte que también sin optimizaciones, los compiladores lo aplican. Pero, por supuesto, no estoy seguro de ello. Por eso pregunto.

    – j00hola

    5 de febrero de 2015 a las 8:02

  • @j00hi: nunca escriba mover en una declaración de devolución: si no se aplica rvo, el valor de devolución se mueve de todos modos de forma predeterminada.

    – Mike MB

    10 de marzo de 2015 a las 1:32

¿Que son la elision de copia y la optimizacion del
Luciano Grigore

Referencia estándar

Para una vista e introducción menos técnica, salte a esta respuesta.

Para los casos comunes en los que se produce la elisión de copia, salte a esta respuesta.

Copiar elisión se define en la norma en:

12.8 Copiar y mover objetos de clase [class.copy]

como

31) Cuando se cumplen ciertos criterios, se permite que una implementación omita la construcción de copiar/mover de un objeto de clase, incluso si el constructor de copiar/mover y/o el destructor del objeto tienen efectos secundarios. En tales casos, la implementación trata el origen y el destino de la operación de copiar/mover omitida como simplemente dos formas diferentes de hacer referencia al mismo objeto, y la destrucción de ese objeto se produce en el último de los momentos en que los dos objetos habrían sido destruido sin la optimización.123 Esta elisión de las operaciones de copiar/mover, denominada copiar elisiónestá permitido en las siguientes circunstancias (que pueden combinarse para eliminar copias múltiples):

— en una declaración de retorno en una función con un tipo de retorno de clase, cuando la expresión es el nombre de un objeto automático no volátil (que no sea una función o un parámetro de cláusula catch) con el mismo tipo cv no calificado que el tipo de retorno de la función, el La operación de copiar/mover se puede omitir mediante la construcción del objeto automático directamente en el valor de retorno de la función.

— en una expresión de lanzamiento, cuando el operando es el nombre de un objeto automático no volátil (que no sea una función o un parámetro de cláusula catch) cuyo alcance no se extienda más allá del final del bloque try que lo encierra más interno (si hay one), la operación de copiar/mover del operando al objeto de excepción (15.1) se puede omitir construyendo el objeto automático directamente en el objeto de excepción

— cuando un objeto de clase temporal que no se ha vinculado a una referencia (12.2) se copiaría/movería a un objeto de clase con el mismo tipo cv no calificado, la operación de copiar/mover se puede omitir construyendo el objeto temporal directamente en el destino de la copia/movimiento omitido

— cuando la declaración de excepción de un manejador de excepciones (Cláusula 15) declara un objeto del mismo tipo (excepto la calificación cv) que el objeto de excepción (15.1), la operación de copiar/mover puede omitirse tratando la declaración de excepción como un alias para el objeto de excepción si el significado del programa no cambiará excepto por la ejecución de constructores y destructores para el objeto declarado por la declaración de excepción.

123) Debido a que solo se destruye un objeto en lugar de dos, y no se ejecuta un constructor de copiar/mover, todavía se destruye un objeto por cada uno construido.

El ejemplo dado es:

class Thing {
public:
  Thing();
  ~Thing();
  Thing(const Thing&);
};
Thing f() {
  Thing t;
  return t;
}
Thing t2 = f();

y explicó:

Aquí los criterios de elisión se pueden combinar para eliminar dos llamadas al constructor de copia de la clase Thing: la copia del objeto automático local t en el objeto temporal para el valor de retorno de la función f()
y la copia de ese objeto temporal en objeto t2. Efectivamente, la construcción del objeto local t
puede verse como inicializando directamente el objeto global t2, y la destrucción de ese objeto ocurrirá al salir del programa. Agregar un constructor de movimiento a Thing tiene el mismo efecto, pero es la construcción de movimiento del objeto temporal a t2 eso es elidido.

  • ¿Es del estándar C++ 17 o de una versión anterior?

    – ceros

    7 de mayo de 2019 a las 12:29

  • ¿Por qué no se puede optimizar el valor de retorno del parámetro de función si es del mismo tipo que el tipo de retorno de la función?

    –Sahil Singh

    20 de junio de 2020 a las 19:26

  • Esto intenta responder – stackoverflow.com/questions/9444485/…

    –Sahil Singh

    20 de junio de 2020 a las 19:48

  • ¿Hay algún tipo de elisión de copia para tipos primitivos? Si tengo una función que propaga un valor de retorno (tal vez un código de error), ¿habrá alguna optimización similar a los objetos?

    – ojiva

    3 de agosto de 2020 a las 12:50

¿Que son la elision de copia y la optimizacion del
Luciano Grigore

Formas comunes de elisión de copia

Para obtener una descripción técnica, salte a esta respuesta.

Para una vista e introducción menos técnica, salte a esta respuesta.

(Nombrado) La optimización del valor de retorno es una forma común de elisión de copias. Se refiere a la situación en la que un objeto devuelto por valor de un método tiene su copia elidida. El ejemplo establecido en la norma ilustra optimización del valor de retorno nombradoya que el objeto tiene nombre.

class Thing {
public:
  Thing();
  ~Thing();
  Thing(const Thing&);
};
Thing f() {
  Thing t;
  return t;
}
Thing t2 = f();

Regular optimización del valor de retorno ocurre cuando se devuelve un temporal:

class Thing {
public:
  Thing();
  ~Thing();
  Thing(const Thing&);
};
Thing f() {
  return Thing();
}
Thing t2 = f();

Otros lugares comunes donde tiene lugar la elisión de copias es cuando un temporal se pasa por valor:

class Thing {
public:
  Thing();
  ~Thing();
  Thing(const Thing&);
};
void foo(Thing t);

foo(Thing());

o cuando un se lanza una excepción y se captura por valor:

struct Thing{
  Thing();
  Thing(const Thing&);
};
 
void foo() {
  Thing c;
  throw c;
}
 
int main() {
  try {
    foo();
  }
  catch(Thing c) {  
  }             
}

Las limitaciones comunes de la elisión de copia son:

  • varios puntos de retorno
  • inicialización condicional

La mayoría de los compiladores de grado comercial admiten la elisión de copia y (N)RVO (según la configuración de optimización).

  • Me interesaría ver las viñetas de “Limitaciones comunes” explicadas un poco… ¿qué hace que estos factores limitantes?

    – etiquetador telefónico

    16 de enero de 2013 a las 17:54

  • @phonetagger Enlacé contra el artículo de msdn, espero que aclare algunas cosas.

    – Lucian Grigore

    16 de enero de 2013 a las 19:11

¿Que son la elision de copia y la optimizacion del
ajay yadav

La elisión de copia es una técnica de optimización del compilador que elimina la copia/movimiento innecesario de objetos.

En las siguientes circunstancias, un compilador puede omitir las operaciones de copiar/mover y, por lo tanto, no llamar al constructor asociado:

  1. NRVO (Optimización de valor de retorno con nombre): si una función devuelve un tipo de clase por valor y la expresión de la declaración de devolución es el nombre de un objeto no volátil con duración de almacenamiento automático (que no es un parámetro de función), entonces la copia/mover que sería realizada por un objeto no volátil -Se puede omitir la optimización del compilador. Si es así, el valor devuelto se construye directamente en el almacenamiento al que, de otro modo, se movería o copiaría el valor devuelto por la función.
  2. RVO (Optimización del valor de retorno): si la función devuelve un objeto temporal sin nombre que un compilador ingenuo movería o copiaría en el destino, la copia o el movimiento se pueden omitir según 1.
#include <iostream>  
using namespace std;

class ABC  
{  
public:   
    const char *a;  
    ABC()  
     { cout<<"Constructor"<<endl; }  
    ABC(const char *ptr)  
     { cout<<"Constructor"<<endl; }  
    ABC(ABC  &obj)  
     { cout<<"copy constructor"<<endl;}  
    ABC(ABC&& obj)  
    { cout<<"Move constructor"<<endl; }  
    ~ABC()  
    { cout<<"Destructor"<<endl; }  
};

ABC fun123()  
{ ABC obj; return obj; }  

ABC xyz123()  
{  return ABC(); }  

int main()  
{  
    ABC abc;  
    ABC obj1(fun123());    //NRVO  
    ABC obj2(xyz123());    //RVO, not NRVO 
    ABC xyz = "Stack Overflow";//RVO  
    return 0;  
}

**Output without -fno-elide-constructors**  
root@ajay-PC:/home/ajay/c++# ./a.out   
Constructor    
Constructor  
Constructor  
Constructor  
Destructor  
Destructor  
Destructor  
Destructor  

**Output with -fno-elide-constructors**  
root@ajay-PC:/home/ajay/c++# g++ -std=c++11 copy_elision.cpp -fno-elide-constructors    
root@ajay-PC:/home/ajay/c++# ./a.out   
Constructor  
Constructor  
Move constructor  
Destructor  
Move constructor  
Destructor  
Constructor  
Move constructor  
Destructor  
Move constructor  
Destructor  
Constructor  
Move constructor  
Destructor  
Destructor  
Destructor  
Destructor  
Destructor  

Incluso cuando se produce la elisión de copia y no se llama al constructor de copia/movimiento, debe estar presente y accesible (como si no hubiera habido ninguna optimización); de lo contrario, el programa tiene un formato incorrecto.

Debe permitir dicha elisión de copia solo en lugares donde no afectará el comportamiento observable de su software. La elisión de copia es la única forma de optimización a la que se le permite tener (es decir, eliminar) efectos secundarios observables. Ejemplo:

#include <iostream>     
int n = 0;    
class ABC     
{  public:  
 ABC(int) {}    
 ABC(const ABC& a) { ++n; } // the copy constructor has a visible side effect    
};                     // it modifies an object with static storage duration    

int main()   
{  
  ABC c1(21); // direct-initialization, calls C::C(42)  
  ABC c2 = ABC(21); // copy-initialization, calls C::C( C(42) )  

  std::cout << n << std::endl; // prints 0 if the copy was elided, 1 otherwise
  return 0;  
}

Output without -fno-elide-constructors  
root@ajay-PC:/home/ayadav# g++ -std=c++11 copy_elision.cpp  
root@ajay-PC:/home/ayadav# ./a.out   
0

Output with -fno-elide-constructors  
root@ajay-PC:/home/ayadav# g++ -std=c++11 copy_elision.cpp -fno-elide-constructors  
root@ajay-PC:/home/ayadav# ./a.out   
1

GCC proporciona la -fno-elide-constructors opción para deshabilitar la elisión de copia. Si desea evitar una posible elisión de copia, use -fno-elide-constructors.

Ahora, casi todos los compiladores brindan elisión de copia cuando la optimización está habilitada (y si no hay otra opción configurada para deshabilitarla).

Conclusión

Con cada elisión de copia, se omiten una construcción y una destrucción coincidente de la copia, lo que ahorra tiempo de CPU, y no se crea un objeto, lo que ahorra espacio en el marco de la pila.

Aquí doy otro ejemplo de elisión de copia que aparentemente encontré hoy.

# include <iostream>


class Obj {
public:
  int var1;
  Obj(){
    std::cout<<"In   Obj()"<<"\n";
    var1 =2;
  };
  Obj(const Obj & org){
    std::cout<<"In   Obj(const Obj & org)"<<"\n";
    var1=org.var1+1;
  };
};

int  main(){

  {
    /*const*/ Obj Obj_instance1;  //const doesn't change anything
    Obj Obj_instance2;
    std::cout<<"assignment:"<<"\n";
    Obj_instance2=Obj(Obj(Obj(Obj(Obj_instance1))))   ;
    // in fact expected: 6, but got 3, because of 'copy elision'
    std::cout<<"Obj_instance2.var1:"<<Obj_instance2.var1<<"\n";
  }

}

Con el resultado:

In   Obj()
In   Obj()
assignment:
In   Obj(const Obj & org)
Obj_instance2.var1:3

  • Eso ya está incluido en la respuesta de Luchian (objeto temporal pasado por valor).

    –Toby Speight

    23 de diciembre de 2020 a las 13:45

  • Eso ya está incluido en la respuesta de Luchian (objeto temporal pasado por valor).

    –Toby Speight

    23 de diciembre de 2020 a las 13:45

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad