Sobrecarga del operador de asignación
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy — lección 44 de 104.
El operador de asignación por copia (=) se llama cuando asignas un objeto existente a otro. A diferencia del constructor de copia (que crea un objeto nuevo), el operador de asignación trabaja con un objeto que ya existe y que puede tener recursos que necesiten ser liberados primero.
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s), data(new int[s]) {}
~Buffer() { delete[] data; }
Buffer& operator=(const Buffer& other) {
if (this != &other) { // 1. Comprobación de autoasignación
delete[] data; // 2. Limpiar el recurso existente
size = other.size; // 3. Copiar los datos
data = new int[size];
for (size_t i = 0; i < size; ++i)
data[i] = other.data[i];
}
return *this; // 4. Devolver *this
}
};La comprobación de autoasignación (this != &other) es crítica. Sin ella, b = b; eliminaría los datos antes de intentar copiarlos, provocando un comportamiento indefinido.
El operador devuelve *this por referencia para permitir el encadenamiento como a = b = c;. Esto coincide con el comportamiento de los tipos integrados.
Para las clases que gestionan recursos, a menudo implementarás tanto los operadores de asignación de copia como los de movimiento:
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data; // Limpiar el recurso actual
data = other.data; // Robar el recurso
size = other.size;
other.data = nullptr; // Dejar el origen en un estado válido
other.size = 0;
}
return *this;
}El operador de asignación por movimiento transfiere la propiedad en lugar de copiar, haciendo que operaciones como vec[0] = createBuffer(); sean mucho más eficientes.
Desafío
FácilVamos a construir una clase DynamicString que gestione un arreglo de caracteres asignado dinámicamente y maneje adecuadamente la asignación entre objetos existentes. Este es un escenario práctico donde necesitas transferir datos de cadena de un objeto a otro de forma segura, sin fugas de memoria ni punteros colgantes.
Crearás dos archivos para organizar tu código:
DynamicString.h: Define una claseDynamicStringque almacene una cadena de estilo C utilizando memoria dinámica. Tu clase debe gestionar:- Un puntero privado
char*para los datos de la cadena - Un
size_tprivado para la longitud - Un constructor que reciba un
const char*y cree una copia profunda (deep copy) - Un destructor que libere adecuadamente la memoria asignada
- Un operador de asignación por copia que realice una copia profunda con protección contra la auto-asignación
- Un operador de asignación por movimiento que transfiera la propiedad de manera eficiente
- Un método
c_str()que devuelva la cadena interna (const) - Un método
length()que devuelva la longitud de la cadena (const)
Tu operador de asignación por copia debe seguir los cuatro pasos esenciales: verificar la auto-asignación, limpiar los recursos existentes, copiar los nuevos datos y devolver
*this. El operador de asignación por movimiento debe robar el puntero del origen y dejar al origen en un estado vacío válido (nullptr con longitud 0).- Un puntero privado
main.cpp: Lee tres cadenas de la entrada (cada una en una línea separada). Crea tres objetosDynamicString:str1con la primera entrada,str2con la segunda entrada ystr3con la tercera entrada.Demuestra tus operadores de asignación:
- Imprime el estado inicial de las tres cadenas
- Usa la asignación por copia:
str1 = str2; - Imprime str1 y str2 después de la asignación por copia (ambos deben tener el mismo contenido)
- Usa la asignación por movimiento:
str2 = std::move(str3); - Imprime str2 después de la asignación por movimiento, y la longitud de str3 (debe ser 0 después de haber sido movido)
Formato de salida:
Initial: str1: <value> str2: <value> str3: <value> After copy (str1 = str2): str1: <value> str2: <value> After move (str2 = std::move(str3)): str2: <value> str3 length: 0
Usa <cstring> para strlen y strcpy. Marca tu operador de asignación por movimiento como noexcept. Incluye <utility> en main.cpp para std::move. No olvides los protectores de inclusión (header guards) en tu archivo de cabecera.
Hoja de referencia
El operador de asignación por copia (=) asigna un objeto existente a otro. Debe encargarse de la limpieza de los recursos existentes antes de copiar los nuevos datos.
Estructura del Operador de Asignación por Copia
ClassName& operator=(const ClassName& other) {
if (this != &other) { // 1. Self-assignment check
// 2. Clean up existing resources
// 3. Copy data from other
}
return *this; // 4. Return *this
}Ejemplo con Memoria Dinámica
class Buffer {
int* data;
size_t size;
public:
Buffer& operator=(const Buffer& other) {
if (this != &other) {
delete[] data; // Clean up existing resource
size = other.size;
data = new int[size];
for (size_t i = 0; i < size; ++i)
data[i] = other.data[i];
}
return *this;
}
};La comprobación de auto-asignación (this != &other) evita que se eliminen los datos antes de copiarlos en casos como b = b;.
Devolver *this por referencia permite el encadenamiento: a = b = c;.
Operador de Asignación por Movimiento
El operador de asignación por movimiento transfiere la propiedad en lugar de copiar, mejorando la eficiencia:
ClassName& operator=(ClassName&& other) noexcept {
if (this != &other) {
// Clean up current resource
// Steal the resource from other
// Leave other in valid state (nullptr, 0, etc.)
}
return *this;
}Ejemplo de Asignación por Movimiento
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data; // Steal the resource
size = other.size;
other.data = nullptr; // Leave source valid
other.size = 0;
}
return *this;
}Marque la asignación por movimiento como noexcept para un rendimiento óptimo con los contenedores estándar.
Pruébalo tú mismo
#include <iostream>
#include <string>
#include <utility>
#include "DynamicString.h"
using namespace std;
int main() {
// Lee tres cadenas de la entrada
string input1, input2, input3;
getline(cin, input1);
getline(cin, input2);
getline(cin, input3);
// Crea tres objetos DynamicString
DynamicString str1(input1.c_str());
DynamicString str2(input2.c_str());
DynamicString str3(input3.c_str());
// TODO: Imprime el estado inicial de las tres cadenas
// Formato:
// Initial:
// str1: <valor>
// str2: <valor>
// str3: <valor>
// TODO: Usa la asignación por copia: str1 = str2;
// TODO: Imprime str1 y str2 después de la asignación por copia
// Formato:
// After copy (str1 = str2):
// str1: <valor>
// str2: <valor>
// TODO: Usa la asignación por movimiento: str2 = std::move(str3);
// TODO: Imprime str2 después del movimiento, y la longitud de str3
// Formato:
// After move (str2 = std::move(str3)):
// str2: <valor>
// str3 length: 0
return 0;
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosConstrucción y compilación en C++Archivos de cabecera y archivos fuenteNamespaces y alcanceIntroducción a OOP en C++Clases vs ObjetosEl puntero 'this'Métodos (Funciones miembro)Atributos (Miembros de datos)Conceptos básicos de Ctors y DtorsResumen - Calculadora simple4Propiedades de clase
Miembros de instancia vs. estáticosGetters y SettersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases amigasResumen - Gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de Ctor y DtorSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualResumen - Jerarquía de empleados2Gestión de memoria
Memoria Stack vs HeapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Resumen - Gestor de arrays dinámicos5Encapsulamiento
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultamiento de informaciónStruct vs ClassClases anidadas e internasResumen - Sistema de registros de estudiantes8Polimorfismo
Polimorfismo: Compilación vs. Tiempo de ejecuciónSobrecarga de funcionesFunciones virtuales revisadasFunciones virtuales purasClases abstractasDiseño de interfaces en C++Dynamic Casting y RTTIResumen: Calculadora de figuras11Conceptos avanzados de POO
Composición vs. HerenciaMixins mediante CRTPIdioma PimplBorrado de tiposEnum Classes y tipado fuerteManejo de excepciones en POOJerarquías de excepciones personalizadas14Patrones de diseño - Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeRAII como patrón3Constructores y Destructores
Constructor por defectoConstructor parametrizadoConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis profundo del destructorRegla de tres / cinco / ceroResumen - Clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujo (Stream)Sobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tiposResumen - Clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasConceptos básicos de SFINAE y Type TraitsResumen - Contenedor genérico