Constructor de copia y sobrecarga II

Constructor por copia y sobrecarga de operadores (ejemplo II).

Implementaremos una clase cCadena, que encapsulará una cadena de caracteres de C. Comenzamos con el header de la clase.

//-----------------------------------------------------------------------------
// TALLER DE PROGRAMACION I
//
// Temas a ejemplificar: constructor de copia, sobrecarga de operadores
//-----------------------------------------------------------------------------
#ifndef _CADENA_H_
#define _CADENA_H_
#include <istream>
 //-----------------------------------------------------------------------------
// Clase cCadena: El objetivo de esta clase es el encapsulamiento de una
//                cadena tipo C (null terminated).
//-----------------------------------------------------------------------------
class cCadena {
 private:
  // enum anonimo para usarlo como constantes
  enum { BUFFER_SIZE = 4096 };
  // representacion interna del string
  char *ptr;
  // funciones privadas utilizadas internamente por los metodos publicos
  void eliminar_actual( void );
  void cargar_cadena( const char *cadena );
  // sobrecarga del operador >>, como funcion no miembro
  friend std::istream &operator>>( std::istream &stream, cCadena &cadena );
  // se sobrecarga el operador == de forma 'no miembro' utilizando una
  // funcion amiga. Ver diferencia con la clase cComplejo, donde este
  // operador es sobrecargado utilizando un metodo miembro
  //
  // NOTA: por fines ilustrativos solo se sobrecargara la comparacion
  // contra un string tipo C. Lo importante a ilustrar tambien es que es
  // sobrecargado dos veces, para poder brindar la reciprocidad, cosa que
  // no seria posible utilizando metodos miembro. La segunda sobrecarga
  // no se realiza mediante funcion amiga.
  friend bool operator==( const cCadena &cadena, const char *string_c );
 public:
  // constructor con parametro con valor por defecto
  // permite instancias de las formas 'cCadena cad', 'cCadena cad( "Hola" )'
  cCadena( const char *cadena = NULL );
  // constructor de copia
  // permite instancias de objetos del tipo 'cCadena cad2( cad1 )'
  cCadena( const cCadena &origen );
  ~cCadena();
  // operador de conversion de tipo a 'const char *'
  operator const char*() const;
  // dos versiones de sobrecarga del operador =, para dos tipos
  // de datos distintos a la derecha del operador
  const cCadena &operator=( const cCadena &origen );
  const cCadena &operator=( const char *origen );
  int obtener_largo() const;
  void borrar();
  // sobrecarga del operador [] utilizada para el acceso a una
  // letra en particular; ej:  char c = cad1[ 5 ];
  const char &operator[]( int posicion ) const;
  char &operator[]( int posicion );
  // operador ! utilizado para saber si la cadena esta vacia
  bool operator!() const;
};
#endif // _CADENA_H_

En un archivo cCadena.cpp implementamos los métodos.

//-----------------------------------------------------------------------------
// TALLER DE PROGRAMACION I
//
// Temas a ejemplificar: constructor de copia, sobrecarga de operadores
//-----------------------------------------------------------------------------
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cassert>
#include "cadena.h"

using namespace std;
void cCadena :: eliminar_actual( void ) {
  // si se posee memoria alocada para alguna cadena,
  // esta es devuelta al sistema
  if( ptr != NULL ) delete[] ptr;
  // la cadena almacenada queda indicada como nula
  ptr = NULL;
}

void cCadena :: cargar_cadena( const char *cadena ) {
  // si actualmente se posee una cadena almacenada, esta
  // debe ser eliminada antes de asignar la nueva
  if( ptr != NULL ) eliminar_actual();
  // si la cadena recibida contiene el valor nulo, nada
  // quedara almacenado
  if( cadena == NULL ) return;
  // se reserva lugar para almacenar la nueva cadena y se
  // almacena como un string null terminated
  ptr = new char[strlen( cadena ) + 1];
  if( ptr != NULL ) {
    strcpy( ptr, cadena );
  } else {
    // aca se podría manejar el caso de error de memoria
    // lanzando una excepcion o algun indicador de error
    // dentro de la clase
  }
}

cCadena :: cCadena( const char *cadena )
 : ptr(NULL) { // Inicializa el atributo privado antes que nada
  // si se envia una cadena de inicializacion (ver uso de parametros
  // por default), se carga la cadena
  if( cadena != NULL ) cargar_cadena( cadena );
}
 
cCadena :: cCadena( const cCadena &origen ):
  ptr(NULL) {
  // si se envia una cadena de inicializacion (ver uso de parametros
  // por default), se carga la cadena
  if( origen.ptr != NULL ) cargar_cadena( origen.ptr );
  // NOTA: En este ejemplo el constructor de copia es necesario. Notar
  // que los punteros no se copian directamente dentro de cargar_cadena,
  // sino que se asigna otro buffer de memoria para almacenar la cadena
}

cCadena :: ~cCadena() {
 // dentro del destructor, se elimina la cadena almacenada (si es
 // que existiese). Notar la importancia: dado que se utiliza
 // memoria dinamica para almacenar las cadenas, se debe devolver
 // la misma antes de eliminar el objeto
 if( ptr != NULL ) eliminar_actual();
}

cCadena :: operator const char*() const {
  // la sobrecarga de este operador de 'conversion de tipos' permite
  // que el objeto cCadena se comporte como un 'const char *' donde
  // se requiera este tipo de dato
  return( (ptr == NULL) ? "" : ptr );
}
 
const cCadena &cCadena :: operator=( const char *origen ) {
  // se carga la nueva cadena a partir de otro objeto del mismo tipo
  cargar_cadena( origen );
  // se devuelve una referencia al mismo objeto para permitir expresiones
  // del tipo: 'a = b = c';
  return( *this );
}

const cCadena &cCadena :: operator=( const cCadena &origen ) {
  // se carga la nueva cadena a partir de otro objeto del mismo tipo
  // NOTA: notar que la condicion ( &origen != this ) evita la
  // auto-asignacion de un objeto sobre si mismo. En este es escencial,
  // porque la destruccion de la cadena actual del objeto destino estaria
  // destruyendo la cadena del objeto origen antes de la asignacion.
  // NOTA: notar que por la sobrecarga del operador de conversion de tipo
  // 'const char *' se puede llamar a la funcion cargar_cadena usando
  // como parametro un objeto del tipo cCadena
  if( &origen != this ) cargar_cadena( origen );
  // se devuelve una referencia al mismo objeto para permitir expresiones
  // del tipo: 'a = b = c';
  return( *this );
}

int cCadena :: obtener_largo() const {
 // devuelve el largo de la cadena, contemplando el caso de la cadena
 // sin inicializar
 return( (ptr == NULL) ? 0 : strlen( ptr ) );
}

void cCadena :: borrar() {
 // elimina la cadena actual, si existiese
 // (libera ademas la memoria alocada para la misma)
 if( ptr != NULL ) eliminar_actual();
}

bool cCadena :: operator!() const {
 // este operador devuelve verdadero si la cadena almacenada esta vacia
 return( obtener_largo() == 0 );
}

const char &cCadena :: operator[]( int posicion ) const {
  // devuelve el i-esimo caracter de la cadena almacenada dentro
  // del objeto, como constante. Ej.  char c = cadena[ 5 ]
  // NOTA: aca se ve una buena razon para tener encapsulada en una clase
  // el manejo de strings. Se posee control sobre los límites del indice,
  // evitando errores de acceso indeseados. Tambien se puede
  // usar std::string accediendo con el metodo at() que tiene chequeo de
  // rango de los indices (lanzando una excepcion out_of_range si esta
  // fuera de rango).

  if( posicion < obtener_largo() ) {
    return( ptr[ posicion ] );
  } else {
    // aca se podría manejar el caso de error de indice fuera de rango
    // lanzando una excepcion, assert, o algun tipo de indicador de error
    // dentro de la clase
    assert( "Indice fuera de Rango" );
    return( ptr[0] ); // aca nunca llegaria
  }
}

char &cCadena :: operator[]( int posicion ) {
  // devuelve el i-esimo caracter de la cadena almacenada dentro
  // del objeto, como destino. Ej.  cadena[ 5 ] = c
  // NOTA: devolver una referencia a char es algo peligroso, porque
  // permitiria que el usuario maliciosamente ingrese un caracter '\0'
  // sin utilizar los metodos destinados a tal fin; una funcionalidad
  // de la clase estaria interfiriendo con el manejo interno de la misma
  if( posicion < obtener_largo() ) {
    return( ptr[ posicion ] );
  } else {
    // aca se podría manejar el caso de error de indice fuera de rango
    // lanzando una excepcion, assert, o algun tipo de indicador de error
    // dentro de la clase
    assert( "Indice fuera de Rango" );
    return( ptr[0] ); // aca nunca llegaria
  }
}
   // notar que esta sobrecarga del operador << (global) no fue declarada
// como friend de la clase cCadena; esto fue posible debido a que esta funcion
// no accede a ningun atributo ni metodo privado de la clase.
ostream &operator<<( ostream &stream, const cCadena &cadena ) {
  // operacion valida debido a que el operador de conversion de tipo
  // 'const char *' fue sobrecargado para la clase cCadena
  const char *cadena_casteada = cadena;
  // realiza la salida de la cadena
  // notar que cout es una instancia de ostream, por lo cual es perfectamente
  // valido 'cout << cadena'
  stream << "Cadena: " << cadena_casteada;
  // devuelve una referencia a ostream para permitir expresiones del tipo
  // 'cout << a << b << c;
  return( stream );
}
 
// notar que esta sobrecarga del operador >> (no miembro) fue declarada
// como friend de la clase cCadena; esto es debido a que esta funcion
// accede a atributos privados de la clase.
istream &operator>>( istream &stream, cCadena &cadena {
  // realiza la entrada a un buffer temporario, con maximo limitado a 500
  // notar que cin es una instancia de istream, por lo cual es perfectamente
  // valido 'cin >> cadena'
  char buffer[cCadena::BUFFER_SIZE];
  stream.getline( buffer, cCadena::BUFFER_SIZE );
  // se realiza la asignacion de la nueva cadena, utilizando metodos publicos
  cadena = buffer;
  // devuelve una referencia a istream para permitir expresiones del tipo
  // 'cin >> a >> b >> c;
  return( stream );
}

// notar que esta sobrecarga del operador == (no miembro) fue declarada
// como friend de la clase cCadena; esto es debido a que esta funcion
// accede a atributos privados de la clase.
bool operator==( const cCadena &cadena, const char *string_c ) {
  // por ser funcion amiga de la clase cCadena, posee acceso a cadena.ptr
  return( strcmp( cadena.ptr, string_c ) == 0 );
}

// notar que esta sobrecarga del operador << (global) NO fue declarada
// como friend de la clase cCadena; esto fue posible debido a que esta funcion
// no accede a ningun atributo ni metodo privado de la clase.
bool operator==( const char *string_c, const cCadena &cadena ) {
  // esta se realiza en base a la anterior, invirtiendo el
  // orden de los parametros (forzando la llamada al operador de cCadena
  return( cadena == string_c );
}

Finalmente utilizamos nuestra clase en un código de pruebas

// -----------------------------------------
//  TEST DE LA CLASE DE CADENAS
// -----------------------------------------
int main( void ) {
  cCadena cad1;
  cCadena cad2( "Hola Mundo" );
  cCadena cad3( cad2 );
  cout << "\n" << cad1 << "\n";
  cout << cad2 << "\n";
  cout << cad3 << "\n\n";
  cad1 = cad2;
  cout << cad1 << "\n";
  cout << cad2 << "\n";
  cout << cad3 << "\n\n";
  if( !cad1 == false )  {
    cout << "La tercer letra es: " << cad1[2] << "\n";
  }
  cad1[2] = 'k';
  if( !cad1 == false ) {
    cout << "La tercer letra es: " << cad1[2] << "\n";
  }
  cad1.borrar();
  if( !cad1 ) {
    cout << "La cadena fue borrada" << "\n\n";
  }
  if( cad2 == "Hola Mundo" ) {
    cout << "Prueba de Reciprocidad (Parte 1)" << "\n";
  }
  if( "Hola Mundo" == cad2 ) {
    cout << "Prueba de Reciprocidad (Parte 2)" << "\n\n";
  }
  cout << "Ingrese Cadena: ";
  cin >> cad1;
  cout << cad1 << "\n\n";
  return( 0 );
}