Skip to content

Instantly share code, notes, and snippets.

@RQF7
Last active March 15, 2018 15:59
Show Gist options
  • Save RQF7/ec2ee580bb59d81e954c766b0c1c8791 to your computer and use it in GitHub Desktop.
Save RQF7/ec2ee580bb59d81e954c766b0c1c8791 to your computer and use it in GitHub Desktop.
Diferencia entre copiar y mover en C++
/*
* Constructores para la clase AES.
* Para simplificar, estoy suponiendo que solo hay un arreglo
* miembro (la llave), en lugar de tres.
*/
/*
* Constructor normal: recibe el tamaño de llave y reserva
* el espacio para el arreglo de la llave.
*/
AES::AES(int tamanioLlave)
: mTamanioLlave {tamanioLlave},
mLlave {new unsigned char [mTamanioLlave]}
{
}
/*
* Constructor por copia: crea un nuevo objeto a partir de
* la referencia dada.
*
* Se usa delegación de constructores: primero se manda a llamar
* al anterior, este reserva memoria, y luego aquí se hace la
* copia solamente
*
* Es importante notar que esta operación toma O(n) pasos en
* llevarse a cabo, ya que es una copia. Perdón por escribir lo obvio:
* al terminar esta operación tienes dos objetos AES con lo
* mismo.
*/
AES::AES(const AES& original)
: AES{original.mTamanioLlave}
{
memcpy(mLlave, original.mLlave, mTamanioLlave);
}
/*
* Asignación por copia.
*/
AES& AES::operator=(const AES& original)
{
mTamanioLlave = original.mTamanioLlave;
delete[] mLlave;
mLlave = new unsigned char[mTamanioLlave];
memcpy(mLlave, original.mLlave, mTamanioLlave);
return *this;
}
/*
* Constructor por movimiento. Igual que la copia, crea
* un nuevo objeto a partir de la referencia dada; la diferencia
* es que esta referencia es de tipo rvalue (una referencia a algo
* que puede ir del lado derecho en una expresión). Creo que no
* acabaría si intento entrar en detalles sobre las diferencias entre
* lvalue y rvalue. Lo importante aquí es que este constructor
* tiene la garantía de que el objeto fuente (original) ya no se va
* a volver a usar, por lo tanto, en lugar de copiar el contenido
* del arreglo, simplemente hay que hacer que el apuntador apunte
* (valga la redundancia) a la misma dirección de memoria.
*
* Importante: esta operación es constante O(1). Al final, queda un
* nuevo objeto AES igual al anterior, y uno vacío, que ya no se
* puede usar.
*
* En la lista de inicialización se inicializa el tamaño de llave y
* el apuntador interno (ojo, solo se actualiza el apuntador, no
* se copia el contenido de todo el arreglo). En el cuerpo se deja
* inutilizable al objeto anterior. Lo importate es hacer nullptr
* su arreglo interno, para que cuando se llame al desctructor,
* este no libere la memoria (ya que esa memoria es la misma que
* está ocupando el nuevo objeto).
*/
AES::AES(AES &&original)
: mTamanioLlave {original.mTamanioLlave},
mLlave {original.mLlave}
{
original.mTamanioLlave = 0;
original.mLlave = nullptr;
}
/*
* Asignación por referencia a rvalue (por movimiento).
*/
AES::operator=(AES &&original)
{
mTmanioLlave = original.mTamanioLlave;
delete[] mLlave; /* Importante: liberar memoria anterior. */
mLlave = original.mLlave;
original.mTamanioLlave = 0;
original.mLlave = nullptr;
return *this;
}
/*
* Destructor. Libera la memoria.
*/
AES::~AES()
{
delete[] mLlave;
}
/*
* Ahora, algunos ejemplos de cuando se manda llamar por defecto
* a cada constructor y a cada operación de asignación:
*/
AES funcionDePrueba(AES argumento);
int main ()
{
/* Constructor normal: */
AES prueba {5};
/* Constructor por copia: */
AES pruebaDos {prueba};
/* Constructor por movimiento: */
AES pruebaTres {std::move(prueba)};
/* Asignación por copia: */
pruebaTres = pruebaDos;
/* Asignación por movimiento: */
pruebaDos = std::move(pruebaTres);
/* Aquí hay varios:
* Primero, se utiliza el constructor por copia para poner
* el contenido de pruebaDos en el contexto de la función.
* Segundo, se utiliza el constructor por movimiento cuando
* la función regresa. */
AES pruebaCinco = funcionDePrueba(pruebaDos);
}
/*
* std::move regresa un rvalue del objeto dado (el nombre
* no es muy descriptivo).
*
* Es importante notar como la operación por movimiento solo
* se utiliza de forma normal cuando las funciones regresan;
* para llamarla de forma explícita hay que utilizar std::move.
*
* Una vez que se conoce cómo funcionan ambas operaciones,
* se pueden ocupar para hacer diseños más eficientes. En particular,
* siempre que se esté seguro de ya no ocupar un objeto, hay que
* utilizar la operación de movimiento (evitando hacer copias
* inútiles). Por ejemplo, considere estas dos versiones de
* una operación de intercambio (swap):
*/
void swapUno(AES uno, AES dos)
{
AES temporal = uno;
uno = dos;
dos = temporal;
}
void swapDos(AES uno, AES dos)
{
AES temporal = std::move(uno);
uno = std::move(dos);
dos = std::move(temporal);
}
/* Ambas hacen lo mismo, sin embargo la primera ocupa copias
* y la segunda movimientos: la primera está en el orden de
* O(3 * n), mentras que la segunda solo actualiza apuntadores
* O(3). Puede parecer una ventaja un tanto menor, sin embargo
* esto depende totalmente del tamaño del arreglo interno; este
* puede tener tanto 10 elementos, como 10 millones, en cuyo caso
* la primera versión del swap es totalmente ineficiente.
*/
@lnborbolla42
Copy link

Ajúa, creo que entonces ys entendí las funciones que me faltaban de la regla de tres (o cinco). Perdona que hayas tenido que explicármelo así tan bebé.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment