Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


 Prev.

4.9.18d  Sobrecarga del operador [ ] (Continuación)

§7  Sinopsis

En la página anterior hemos visto algunos ejemplos de sobrecarga de operadores, aunque en realidad, solo el selector de miembro [] pertenece a la clase mVector. Los demás pertenecen a la clase auxiliar Vector ya comentada en repetidas ocasiones.

Puesto que ya tenemos las herramientas necesarias, continuaremos desarrollando la clase mVector, proporcionándole los operadores necesarios para efectuar las operaciones siguientes:

mVector mv1, mv2;
mVector mv3 = mv1 + mv2;

Evidentemente necesitamos los operadores suma + ( 4.9.18b) y asignación = ( 4.9.18a) para objetos mVector, así como un constructor-copia ( 4.11.2d4) adecuado. Empezaremos por este último.


§8  Constructor-copia

Recordemos lo dicho en la página anterior:  "el constructor crea una matriz de vectores del tamaño indicado en el argumento y la señala con el puntero mVptr. Esta matriz está alojada en memoria persistente y en cierta forma podríamos pensar que es "externa" a la propia estructura; que esta realmente solo contiene un puntero...". La situación se esquematiza en la figura 1.


Cuando los objetos son de estas características, la copia y asignación [1] pueden realizarse siguiendo dos criterios que se han esquematizado en la figura 2.

La opción A, supone considerar el objeto como puntero. En una asignación obj2 = obj1, el receptor de la asignación señala en realidad a los mismos datos que el objeto del que recibe el valor.

En determinados contextos esta política puede ser válida pero no exenta de inconvenientes. Por ejemplo, la destrucción de obj-1 supone la liberación del espacio ocupado por Matriz-1. ¿Que pasa con el puntero de Objeto-2 que señala a un sitio erróneo?.


La opción B supone considerar el objeto como valor. En este tipo de asignaciones existe independencia real entre los objetos resultantes, de modo que las posteriores modificaciones en el primero no afectan al segundo. En estas circunstancias, si se trata de una copia no hay que preocuparse (obj-2 es creado de nuevo), pero si se trata de una asignación hay que cuidarse de destruir la matriz previamente señalada por obj-2, reservar un nuevo espacio y asignarle los valores de Matriz-1.


§8.1  A continuación se incluye la solución propuesta para el constructor-copia, que considera el objeto "como valor".

mVector(const mVector& mv) {  // constructor-copia
  dimension = mv.dimension;           // dimensiones iguales
  mVptr = new Vector[dimension];      // Reserva nuevo espacio
  for(int i = 0; i<dimension; i++) {  // copia miembro a miembro
    mVptr[i]= mv.mVptr[i];            // L.5
  }
}

Recuerde que los objetos mVector son matrices de objetos tipoX y que L.5 supone la asignación entre los miembros de estas matrices [2], lo que implica a su vez que debe estar definido el operador de asignación entre objetos tipoX (en el caso que nos ocupa se trata de objetos tipo Vector).

§9  Operador de asignación

A continuación se incluye una versión del operador de asignación para la clase mVector considerando los objetos "como valor".

mVector& operator=(const mVector& mv) {  // Operador =
  delete [] mVptr;                       // rehusar espacio previo
  dimension = mv.dimension;              // dimensiones iguales
  mVptr = new Vector[dimension];         // Reserva nuevo espacio
  for(int i = 0; i<dimension; i++) {     // copia miembro a miembro
    mVptr[i]= mv.mVptr[i];
  }
  return *this;
}

Observe que es muy parecido al constructor-copia, aunque este no crea ningún objeto nuevo (no es un constructor), e incluye una primera sentencia encargada de rehusar el espacio de la matriz previamente señalada por el objeto que recibe la asignación, y que se devuelve una referencia, con objeto de que el valor resultante pueda ser utilizado Rvalue y Lvalue en operaciones de asignación encadenadas ( 4.9.18a).


§10  A este respecto debemos recordar que en el diseño actual de mVector (página anterior), el constructor por defecto es:

mVector(int n = 1) {   // constructor por defecto
  dimension = n;
  mVptr = new Vector[dimension];
}

En estas condiciones, cualquier objeto mVector tiene tamaño 1 si se ha utilizado el argumento por defecto del constructor. Por ejemplo, en una sentencia del tipo mVector mv;. En cambio, si se utiliza la asignación: mVector mv = mVector(0);, aunque la petición es de espacio cero, new devuelve un puntero no nulo ( 4.9.20), y es posible todavía utilizar la versión anterior para el operador de asignación.

Supongamos sin embargo, que el diseño del constructor es el siguiente:

mVector(int n = 0) {    // constructor por defecto
  dimension = n;
  if (n == 0) mVptr = 0;
  else        mVptr = new Vector[dimension];
}

En estas condiciones pueden existir objetos mVector a los que no corresponda un espacio en memoria persistente. En consecuencia es necesario modificar nuestra versión del operador de asignación:

mVector& operator=(const mVector& mv) {   // Operador =
  if ( mVptr != 0 ) { delete [] mVptr; }  // rehusar espacio previo
  dimension = mv.dimension;               // dimensiones iguales
  if (dimension != 0 ) {
    mVptr = new Vector[dimension];        // Reserva nuevo espacio
    for(int i = 0; i<dimension; i++) {    // copia miembro a miembro
      mVptr[i]= mv.mVptr[i];
    }
  }
  return *this;
}


También sería necesario modificar el constructor-copia:

mVector(const mVector& mv) {  // constructor-copia
  dimension = mv.dimension;             // dimensiones iguales
  if ( dimension == 0 ) { mVptr == 0; }
  else {
    mVptr = new Vector[dimension];      // Reserva nuevo espacio
    for(int i = 0; i<dimension; i++) {  // copia miembro a miembro
      mVptr[i]= mv.mVptr[i];
    }
  }
}

§11  Versión operativa

Después de incluir los nuevos operadores, la versión definitiva de mVector es como sigue:

#include <iostream>
using namespace std;

class Vector {    // Clase auxiliar Vector
  public: int x, y;
  Vector& operator= (const Vector& v) {   // asignación V = V
    x = v.x; y = v.y;
    return *this;
  }
  void showV() { cout << "X = " << x << "; Y = " << y << endl; }
};

class mVector {   // clase mVector
  int dimension;
  public:
  Vector* mVptr;
  mVector& operator=(const mVector& mv) { // Operador =
    delete [] mVptr;
    dimension = mv.dimension;
    mVptr = new Vector[dimension];
    for(int i = 0; i<dimension; i++) {
      mVptr[i]= mv.mVptr[i];
    }
    return *this;
  }
  mVector(int n = 1) {          // constructor por defecto
    dimension = n;
    mVptr = new Vector[dimension];
  }
  ~mVector() {                  // destructor
    delete [] mVptr;
  }
  mVector(const mVector& mv) {  // constructor-copia
    dimension = mv.dimension;
    mVptr = new Vector[dimension];
    for(int i = 0; i<dimension; i++) {
      mVptr[i]= mv.mVptr[i];    // L.5
    }
  }
  Vector& operator[](int i) { return mVptr[i]; }
  void showmem (int);           // L.31: función auxilar
  void show ();                 // función auxilar
};

void mVector::showmem (int i) { // Ver miembro
  if((i >= 0) && (i <= dimension)) mVptr[i].showV();
  else cout << "Argumento incorrecto! pruebe otra vez" << endl;
}

void mVector::show () {         // Ver objeto completo
  cout << "Matriz de: " << dimension << " elementos." << endl;
  for (int i = 0; i<dimension; i++) {
    cout << i << "- ";
    mVptr[i].showV();
  }
}

void main() {     // =====================
  mVector mV1(5);              // instanciar una matriz de 5 elementos
  mV1[0].x = 0; mV1[0].y = 1;  // iniciar con ciertos valores
  mV1[1].x = 2; mV1[1].y = 3;
  mV1[2].x = 4; mV1[2].y = 5;
  mV1[3].x = 6; mV1[3].y = 7;
  mV1[4].x = 8; mV1[4].y = 9;
  mV1.show();                  // S.1: mostrar objeto
  mVector mV2 = mV1;           // usar el constructor-copia
  mV2.show();                  // S.2: verificar el resultado
  mV1[0].x = 9; mV1[0].y = 0;  // modificar miembro
  mV2.showmem(0);              // S.3: mostrar miembro (no modificado)
  mV1.showmem(0);              // S.4: mostrar miembro (modificado)
  mVector mV3(0);              // instanciar matriz de 0 elementos
  mV3.show();                  // S.5: mostrar objeto
  mV3 = mV1;                   // asignación
  mV3.show();                  // S.6: mostrar objeto
}

Salida:

Matriz de: 5 elementos.     S.1
0- X = 0; Y = 1
1- X = 2; Y = 3
2- X = 4; Y = 5
3- X = 6; Y = 7
4- X = 8; Y = 9
Matriz de: 5 elementos.     S.2
0- X = 0; Y = 1
1- X = 2; Y = 3
2- X = 4; Y = 5
3- X = 6; Y = 7
4- X = 8; Y = 9
X = 0; Y = 1                S.3
X = 9; Y = 0                S.4
Matriz de: 0 elementos.     S.5
Matriz de: 5 elementos.     S.6
0- X = 9; Y = 0
1- X = 2; Y = 3
2- X = 4; Y = 5
3- X = 6; Y = 7
4- X = 8; Y = 9

Comentario

Comprobamos que en la función main hemos efectuado todas las operaciones que pretendíamos al principio con objetos de tipo mVector, y que los resultados son los esperados.

Merecen especial atención las salidas S.5  y S.6 como comprobación de que efectivamente la asignación se hace "por valor" ya que después de realizada, la modificación del primer miembro no modifica al segundo.

En la página adjunta se expone un ejemplo que utiliza la indirección ( 4.9.16) y la sobrecarga del operador de subíndice [ ] para controlar el acceso a miembros dentro de los límites de una matriz. Ejemplo.

  Inicio.


[1]  Ambas son "asignaciones".  La diferencia estriba en que la copia (realizada por el constructor-copia) crea un objeto y luego le asigna los valores de otro, mientras que la asignación asigna los valores de un objeto a otro ya existente.  Podría considerarse que la copia consiste en una creación + asignación.

[2]  Aplicamos a los punteros el álgebra de subíndices (como si fuesen matrices 4.3.2).

 Prev.