c++ - sinalefa - ¿Qué es elisión de copia y cómo se optimiza el modismo de copiar y cambiar?
sinalefa ejemplos wikipedia (2)
Estaba leyendo Copiar e Intercambiar .
Intenté leer algunos enlaces en Copy Elision, pero no pude entender correctamente lo que significaba. ¿Puede alguien explicar por favor qué es esta optimización, y especialmente qué significa con el siguiente texto?
Esto no es solo una cuestión de conveniencia, sino de hecho una optimización. Si el parámetro (s) se une a un lvalue (otro objeto no const), se crea automáticamente una copia del objeto al crear el (los) parámetro (s). Sin embargo, cuando s se une a un valor r (objeto temporal, literal), la copia generalmente se elimina, lo que guarda una llamada a un constructor de copia y un destructor. En la versión anterior del operador de asignación, donde el parámetro se acepta como referencia constante, la elisión de copia no ocurre cuando la referencia se une a un valor r. Esto resulta en un objeto adicional que se crea y destruye.
Aquí hay un ejemplo:
#include <vector>
#include <climits>
class BigCounter {
public:
BigCounter &operator =(BigCounter b) {
swap(b);
return *this;
}
BigCounter next() const;
void swap(BigCounter &b) {
vals_.swap(b);
}
private:
typedef ::std::vector<unsigned int> valvec_t;
valvec_t vals_;
};
BigCounter BigCounter::next() const
{
BigCounter newcounter(*this);
unsigned int carry = 1;
for (valvec_t::iterator i = newcounter.vals_.begin();
carry > 0 && i != newcounter.vals_.end();
++i)
{
if (*i <= (UINT_MAX - carry)) {
*i += carry;
} else {
*i += carry;
carry = 1;
}
}
if (carry > 0) {
newcounter.vals_.push_back(carry);
}
return newcounter;
}
void someFunction()
{
BigCounter loopcount;
while (true) {
loopcount = loopcount.next();
}
}
En somefunction
la línea loopcount = loopcount.next();
se beneficia enormemente de la elisión de copia. Si no se permitiera la elisión de copia, esa línea requeriría 3 invocaciones del constructor de copia y una llamada asociada a un destructor. Con la elisión de copia permitida, se puede reducir a 1 llamada del constructor de copia, la explícita dentro de BigCount::next()
donde se declara newcounter
.
Si operator =
ha sido declarado y definido así:
BigCounter &BigCounter::operator =(const BigCounter &b) {
BigCounter tmp(b);
swap(tmp);
return *this;
}
habría tenido que haber sido 2 invocaciones del constructor de copia, incluso con elisión de copia. Uno para construir newcounter
y el otro para construir tmp
. Y sin copia elisión todavía habría 3. Por eso, declarar operator =
entonces su argumento requiere invocar el constructo de copia puede ser una optimización cuando se usa el modismo ''copiar y intercambiar'' para el operador de asignación. Cuando se invoca el constructor de copia para construir un argumento, su invocación puede ser eliminada, pero si se invoca para crear una variable local, puede no serlo.
El constructor de copia existe para hacer copias. En teoría, cuando escribes una línea como:
CLASS c(foo());
El compilador debería llamar al constructor de copia para copiar el retorno de foo()
en c
.
La elisión de copia es una técnica para omitir la llamada al constructor de copia para no pagar la sobrecarga.
Por ejemplo, el compilador puede organizar que foo()
construya directamente su valor de retorno en c
.
Aquí hay otro ejemplo. Digamos que tienes una función:
void doit(CLASS c);
Si lo llama con un argumento real, el compilador debe invocar el constructor de copia para que el parámetro original no se pueda modificar:
CLASS c1;
doit(c1);
Pero ahora considere un ejemplo diferente, digamos que llama a su función de esta manera:
doit(c1 + c1);
operator+
va a tener que crear un objeto temporal (un valor r). En lugar de invocar el constructor de copia antes de llamar a doit()
, el compilador puede pasar el temporal creado por operator+
y pasarlo a doit()
lugar.