Lección 8 · 8 de 20
Copiar y mover · C++
Distinga entre copiar y mover en C++: la regla de los cinco, std::move y noexcept, con una imagen que gestiona su propia memoria.

Image reserva sus píxeles con new int[size]() y los libera en el destructor. Una clase que gestiona memoria a mano necesita la regla de los cinco: si escribe el destructor, escriba también el constructor de copia, el de movimiento y las asignaciones de copia y de movimiento. Si no, la copia que genera el compilador copiaría solo el puntero, y las dos imágenes liberarían la misma memoria.
El constructor de copia, Image(const Image& o), hace una copia profunda: reserva memoria nueva y copia cada píxel. Es lo que ocurre en Image backup = photo;, y cuesta copiar dos millones de valores.
El constructor de movimiento, Image(Image&& o), recibe una referencia a rvalue (&&), un objeto que ya no se va a usar. En vez de copiar, se queda con su puntero y deja el original vacío pero válido (nullptr y tamaño 0). `std::move(photo)` no mueve nada por sí solo: solo indica que se puede robar de ahí, y después photo.size() vale 0. Se marca `noexcept` para que std::vector se atreva a mover en lugar de copiar cuando crece.
En album.push_back(Image(640 * 480)) la imagen es un temporal, así que se mueve sin pedirlo; con std::move(backup) se mueve también una variable con nombre. reserve(2) evita que el vector se amplíe a mitad y mueva elementos de más.
La asignación usa el modismo copiar e intercambiar: operator=(Image o) recibe el parámetro por valor (copiado o movido, según lo que se le pase) y lo intercambia con std::swap; el destructor de o libera los píxeles viejos. Una sola función cubre las dos asignaciones. En la práctica, si usa std::vector o punteros inteligentes en lugar de new, no tendrá que escribir ninguna de las cinco: es la regla del cero.
Código · main.cpp
#include <iostream>#include <utility>#include <vector>class Image { // owns a raw buffer, so it needs the rule of five int size_; int* pixels_;public: explicit Image(int size) : size_(size), pixels_(new int[size]()) {} ~Image() { delete[] pixels_; } // 1 destructor Image(const Image& o) : size_(o.size_), pixels_(new int[o.size_]) { // 2 copy: deep for (int i = 0; i < size_; ++i) pixels_[i] = o.pixels_[i]; std::cout << "copy " << size_ << " pixels\n"; } Image(Image&& o) noexcept : size_(o.size_), pixels_(o.pixels_) { // 3 move: steal o.size_ = 0; o.pixels_ = nullptr; std::cout << "move, no copy\n"; } Image& operator=(Image o) noexcept { // 4 and 5: copy and move assignment std::swap(size_, o.size_); std::swap(pixels_, o.pixels_); return *this; } int size() const { return size_; }};int main() { Image photo(1920 * 1080); Image backup = photo; // copy constructor Image thumb = std::move(photo); // move constructor: photo is left empty std::cout << "photo " << photo.size() << ", backup " << backup.size() << ", thumb " << thumb.size() << "\n"; std::vector<Image> album; album.reserve(2); album.push_back(Image(640 * 480)); // a temporary is moved, not copied album.push_back(std::move(backup)); backup = thumb; // copy assignment: o is a copy of thumb std::cout << "album " << album.size() << ", backup " << backup.size() << "\n";}Salida · c++ -std=c++17 main.cpp -o main && ./main
copy 2073600 pixels move, no copy photo 0, backup 2073600, thumb 2073600 move, no copy move, no copy copy 2073600 pixels album 2, backup 2073600
Para recordar
- Copiar duplica los datos; mover traspasa los recursos y deja el original vacío.
T(const T&)copia yT(T&&)mueve.std::move(x)conviertexen algo que se puede mover; no lo use después salvo para asignarle.- Marque el constructor de movimiento como
noexcept. - Regla de los cinco si gestiona recursos a mano; regla del cero si no.
Ejercicio
Quite album.reserve(2); y vuelva a ejecutar el programa. Cuente los mensajes move de más que aparecen y explique de dónde salen.