Sei sulla pagina 1di 13

2.1.

2 Sobrecarga del operador predecremento --@


Como ejemplo incluimos una versin de la clase anterior en la que sobrecargamos los
operadores preincremento y predecremento, pero utilizando la posibilidad 2.1b enunciada al
principio . Es decir, mediante una funcin-operador externa que acepte un argumento.
Nota: aunque no es necesario, porque la nica propiedad de la clase es pblica, hemos
declarado las funciones-operador como friend de la clase. Esto es lo usual, porque as se
garantiza el acceso a los miembros, incluso privados, de la clase desde la funcin.
#include <iostream>
using namespace std;

class Entero {
public: int x;
friend Entero& operator++(Entero&);
friend Entero& operator--(Entero&);
};
Entero& operator++ (Entero& e) {
e.x = e.x + e.x;
return e;
}
Entero& operator-- (Entero& e) {
e.x = e.x / 2;
return e;
}

void main () { // ==============
Entero e1, e2, e3;
e1.x = 5;
e3 = ++e2 = ++e1;
cout << " e1 = " << e1.x << "; e2 = " << e2.x
<< "; e3 = " << e3.x << endl;
e3 = --e2 = --e1;
cout << " e1 = " << e1.x << "; e2 = " << e2.x
<< "; e3 = " << e3.x << endl;
}
Salida:
e1 = 10; e2 = 10; e3 = 10
e1 = 5; e2 = 5; e3 = 5

2.2 Sobrecarga de los post-operadores X++ X--
Los postoperadores incremento ++ y decremento -- solo pueden ser sobrecargados definiendo las
funciones-operador de dos formas:
2.2a. Declarando una funcin miembro no esttica que acepte un entero como
argumento. Ejemplo:
C C::operator++(int);
2.2b. Declarando una funcin no miembro (generalmente friend) que acepte un objeto de la clase
y un entero como argumentos (en este orden). Ejemplo:
C operator-- (C&, int);
Segn lo anterior, y dependiendo de la declaracin, si @ representa un post-operador unitario
(++ o --), la expresin x@ puede ser interpretada como cualquiera de las dos formas:
2.2a: x.operator@(int)
2.2b: operator@(x, int)
Nota: debemos advertir que la inclusin del entero como argumento es simplemente un
recurso de los diseadores del lenguaje para que el compilador pueda distinguir las
definiciones de los "pre" y "post" operadores ++ y -- (el argumento no se usa para nada ms).
De hecho, las primeras versiones C++ (hasta la versin 2.0 del preprocesador cfront de
AT&T), no distinguan entre las versiones sobrecargadas "pre" y "post" de los operadores
incremento y decremento.
Para ilustrar el proceso, extendemos el ejemplo de la clase Entero sobrecargando los
operadores postincremento y postdecremento. Mantenemos la misma lgica que establecimos
con los preoperadores: el incremento aumenta al doble el valor de la propiedad x, y el decremento
lo disminuye a la mitad. Para la definicin del primero utilizamos la solucin 2.2a, y la 2.2b para el
segundo.
#include <iostream>
using namespace std;

class Entero {
public: int x;
friend Entero& operator++(Entero&); // L.6: preincremento
friend Entero& operator--(Entero&); // L.7: predecremento
Entero operator++(int i) { // L.8: postincremento
Entero tmp = *this; // L.9:
x = x + x; return tmp;
}
friend Entero operator--(Entero&, int); // L.12: postdecremento
};

Entero& operator++ (Entero& e) { // preincremento
e.x = e.x + e.x; return e;
}
Entero& operator-- (Entero& e) { // predecremento
e.x = e.x / 2; return e;
}
Entero operator-- (Entero& e, int i) { // L.21: postdecremento
Entero tmp = e; // L.22:
e.x = e.x / 2; return tmp;
}

void main () { // ===========
Entero e1 = { 6 }, e2; // M.1
e2 = e1++; // M.2
cout << " e1 = " << e1.x << " e2 = " << e2.x << endl;
e2 = e1--; // M.4
cout << " e1 = " << e1.x << " e2 = " << e2.x << endl;
}
Salida:
e1 = 12 e2 = 6
e1 = 6 e2 = 12
Comentario
En la definicin de los operadores preincremento (L.6) y predecremento (L.7) se ha utilizado la
frmula 2.1b : "declarar una funcin no miembro que acepte un argumento". Para
postincremento (L.8), se ha utilizado la opcin 2.2a "funcin miembro no esttica que acepte un
entero como argumento". Finalmente, para el posdecremento (L.12) se ha utilizado la
opcin 2.2b "una funcin no miembro que acepte un objeto de la clase y un entero".
Puede comprobarse que las salidas son las esperadas para los operadores. En M.2 se asigna el
valor inicial de e1 a e2 (en este momento e1.xtiene el valor 6). A continuacin se incrementa e1,
con lo que el valor e1.x pasa a ser 12. Estos son los resultados mostrados en la primera salida.
En M.4 el valor inicial de e1 es asignado a e2 (ahora e1.x tiene valor 12). A continuacin e1 es
decrementado, con lo que el valor final de e1.xes 6; los resultado se ven en la segunda salida.
Es interesante observar que los operadores "post" incremento/decremento presentan una dificultad
terica al ser tratados como funciones: se precisa un mecanismo que aplique el resultado exigido,
pero devuelva el objeto en su estado "anterior" a la aplicacin del mismo.
En realidad las definiciones de las funciones operator++ y operator-- de L.8 y L.21 intentan
mimetizar este comportamiento mediante un objeto temporal tmp, que es creado antes que nada
con el contenido del objeto sobre el que se aplicar el operador. Observe que en L.9 nos referimos
a l mediante *this, mientras que en L.22 es el objeto pasado explcitamente como argumento.
Este objeto temporal tmp es el que realmente se devuelve, con lo que la funcin-operador
devuelve un objeto anlogo al operando antes de la modificacin, al mismo tiempo que las
modifcaciones se realizan sobre el objeto original. En nuestro caso, la modificacin de la
propiedad x del operando, se realiza como:
x = x + x; // L.10:
cuando se trata de una funcin-miembro, y como:
e.x = e.x / 2; // L.23:
cuando la funcin-operador no es miembro de la clase.
Ntese que en este ltimo caso, el objeto pasado como argumento debe serlo como referencia
(L.12 y L.21). La razn es que la funcin-operador debe modificar el valor del objeto pasado como
argumento ( 4.2.3).
Observe tambin que este diseo permite la existencia de varias funciones-operador post-
incremento / post-decremento referidas a clases distintas. El mecanismo de sobrecarga permitir al
compilador saber de que funcin se trata a travs del anlisis de sus argumentos.
Nota: el diseo de los operadores "post" presenta una importante dificultad terica: en ambos
casos es necesario devolver un valor, no una referencia. Esto hace que el resultado no pueda ser
utilizado al lado izquierdo de una asignacin (como un Lvalue). Es decir, no son posibles
asignaciones del tipo:
e2++ = e1; // Error: "Lvalue required"
por tanto tampoco son posibles expresiones de asignacin compuesta del tipo:
e3 = e2++ = e1++; // Error:
que s son posibles con los pre-operadores . Esta limitacin es extensiva incluso a los tipos
bsicos; tampoco con ellos son posibles expresiones del tipo:
int x = 5, y, z;
y++ = x; // Error: "Lvalue required"
z = y++ = x++; // Error:
mientras que las anlogas con preincremento y predecremento s son posibles :
z = ++y = ++x; // Ok.

En la pgina adjunta se expone un ejemplo de una tcnica que utiliza la indireccin ( 4.9.16)
y la sobrecarga de operadores unarios para simular una sobrecarga de los operadores ++ y --
sobre punteros; algo que como se ha indicado ( 4.9.18), no es en principio posible Ejemplo.

3 Sobrecarga del operador de indireccin
Recordemos que el operador de indireccin * ( 4.9.11) es un preoperador unario, por lo que su
sobrecarga puede ser efectuada de cualquiera de las formas 2.1a o 2.1b . En la pgina adjunta
se incluye un completo ejemplo de su utilizacin ( Ejemplo).

4 Sobrecarga del operador de negacin lgica
La sobrecarga del operador ! NOT de negacin lgica puede verse en el epgrafe ( 4.9.18g)
junto con las sobrecargas del resto de operadores lgicos (binarios).
4.9.18d Sobrecarga del operador [ ]
1 Sinopsis
Recordemos ( 4.9.16) que este operador sirve para sealar subndices de matrices simples y
multidimensionales; de ah su nombre, operadorsubndice o de seleccin de miembro de matriz.
La expresin:
<exp1>[exp2]
se define como: *((exp1) + (exp2)) donde exp1 es un puntero y exp2 es un entero o
viceversa.
Por ejemplo, arrX[3] se define como: *(arrX + 3) o *(3 + arrX), donde arrX es un
puntero al primer elemento de la matriz. (arrX + 3) es un puntero al cuarto elemento, y *(arrX
+ 3) es el valor del cuarto elemento de la matriz.
Lo anterior puede sintetizarse en la siguiente relacin:
arrX[3] == *(arrX + 3) // 1a

2 Cuando se utiliza con tipos definidos por el usuario, este operador puede ser sobrecargado
mediante la funcin operator[ ]( ) ( 4.9.18).Para ilustrarlo con un ejemplo, utilizaremos la
clase mVector que contiene una matriz (es una matriz de vectores), y suponemos que los
elementos de la matriz son vectores deslizantes de un espacio bidimensional. El diseo bsico es
el que se indica:
class Vector { // definicin de la clase Vector
public: int x, y;
};

class mVector { // definicin de la clase mVector
public:
Vector* mVptr; // L.6:
mVector(int n = 1) { // constructor por defecto
mVptr = new Vector[n]; // L.8:
}
~mVector() { // destructor
delete [] mVptr;
}
};
Comentario
La clase Vector tiene solo dos miembros, que suponemos las componentes escalares de cada
vector del plano. Por simplicidad hemos supuesto que son int, pero podran ser otros tipos de
punto flotante, por ejemplo float o double. Esta clase auxiliar la hemos definido externa e
independiente de la clase mVector.
Tambin podra utilizarse otro diseo en el que Vector estuviese definida "dentro" de la
clase mVector. Las diferencias entre ambos y los criterios de uso se discuten en ( 4.13.2):
class mVector { // definicin de la clase mVector
...
class Vector { // clase anidada
...
};
...
};

La clase mVector tiene un solo miembro; un puntero-a-Vector mVptr (L.6). Tambin definimos un
constructor por defecto y un destructor.
Observe (L.8) que el constructor del objeto tipo mVector, crea una matriz de objetos
tipo Vector del tamao indicado en el argumento (1 por defecto) y la seala con el
puntero mVptr. Esta matriz est alojada en memoria persistente ( 4.9.20c) y en cierta forma
podramos pensar que es "externa" al objeto, ya que este realmente solo contiene un puntero [1].
Precisamente en razn de esta persistencia, el destructor debe rehusar la memoria asignada a la
matriz, pues de otro modo este espacio se perdera al ser destruido el objeto ( 4.9.21).
Siguiendo el paradigma de la POO, esta clase deber contener los datos (la matriz) y los
algoritmos (mtodos) para manejarla. Deseamos utilizar los objetos de tipo mVector como
autnticas matrices, por lo que deberamos poder utilizarlos con lgebra de matrices C++.
Utilizando una analoga, si por ejemplo m es una matriz de enteros, sabemos que el lenguaje nos
permite utilizar las expresiones siguientes:
m[i]; // L.1: acceso a elemento con el operador subndice
int x = m[i]; // L.2: asignacin a un miembro de la clase int
m[i] = m[j]; // L.3: asignacin a miembro
m[i] = 3 * m[j]; // L.4: producto por un escalar
m[i] = m[j] * m[k]; // L.5: producto entre miembros
En consecuencia, debemos preparar el diseo de la clase mVector de forma que que pueda
mimetizarse el comportamiento anterior con sus objetos. Es decir, deben permitirse las siguientes
expresiones:
mVector m1;
m1[i]; // acceso a elemento con el operador subndice
Vector v1 = m1[i]; // asignacin a un miembro de la clase Vector
m1[i] = m1[j]; // asignacin a miembro
m1[i] = 3 * m1[j]; // producto por un escalar
m1[i] = m1[j] * m1[k]; // producto entre miembros
3 Operador subndice
Para mimetizar este comportamiento con los objetos de la nueva clase empezaremos por poder
referenciarlos mediante el operador subndice [ ](L.1 ). Este operador debe recibir un int y
devolver el miembro correspondiente de la matriz (recordemos que los miembros son
tipo Vector). Como sabemos que debe gozar del doble carcter de Rvalue y Lvalue (L.3 ),
deducimos que debe devolver una referencia ( 4.9.18c). A "vote pronto" podra parecernos que
la definicin debe ser del tipo:
const size_t sV = sizeof(Vector);
Vector& operator[](int i) { return *( mVptr + (i * sV)); }
sin embargo, reflexionando ms detenidamente recordamos que mVptr est definido
precisamente como puntero-a-Vector, por lo que su lgebra lleva implcito el tamao de los
objetos Vector ( 4.2.2), lo que significa que podemos prescindir del factor sV:
Vector& operator[](int i) { return *( mVptr + i ); }
Recordando la definicin de subndice 1a , y la relacin entre punteros y matrices ( 4.3.2) la
expresin anterior equivale a:
Vector& operator[](int i) { return mVptr[i]; }
esta es justamente la definicin que utilizamos para la funcin-operador operator[ ] ( L.30) de
nuestra clase. Como resultado, podemos utilizar expresiones del tipo:
mVector mV1(5); // objeto tipo mVector (matriz de 5 Vectores)
mV1[2]; // tercer elemento de la matriz

4 Operador de asignacin
Para utilizar el operador de asignacin = con los objetos devueltos por el selector de miembro [ ],
debemos sobrecargarlo para los objetos tipoVector. Esto se ha visto en el epgrafe
correspondiente, por lo que nos limitamos a copiar dicha definicin ( 4.9.18a):
Vector& operator= (const Vector& v) { // funcin operator=
x = v.x; y = v.y;
return *this;
}
Su implementacin en la versin definitiva ( L.6), nos permite utilizar expresiones del tipo:
Vector v1;
v1 = mV1[0]; // M.6:

5 Producto por un escalar
Para mimetizar el comportamiento expresado en L.4 , sobrecargamos el operador producto para
la clase Vector de forma que acepte un int. La definicin la hacemos de forma que corresponda a
la definicin tradicional. Es decir, la resultante es un vector cuyos componentes son el producto de
los componentes x y del vector operando por el escalar.
Es importante observar aqu que, en el caso de la matriz de enteros m, las dos sentencias
siguientes son equivalentes:
m[i] = m[j] * 3; // producto por un escalar (por la derecha)
m[i] = 3 * m[j]; // producto por un escalar (por la izquierda)

5.1 Esto significa que debemos definir el producto en ambos sentidos. Para el primero podemos
definir una funcin miembro que acepte un argumento tipo int (adems del correspondiente
puntero this). Este mtodo tiene el aspecto que se indica:
Vector operator* (int i) { // producto por un escalar (por la derecha)
Vector vr;
vr.x = x * i;
vr.y = y * i;
return vr;
}
Despus de implementado en la versin definitiva ( L.9), nos permite expresiones del tipo:
mV1[4] = mV1[0] * 5; // M.8:

5.2 El producto por la izquierda debemos definirlo como una funcin-operador externa. Se trata
de una funcin independiente (no pertenece a una clase) que acepta dos argumentos, un int y
un Vector. Como es usual, la declaramos friend de la clase Vector ( L.15) para que pueda
tener acceso a sus miembros (aunque en este caso no es necesario porque todos son pblicos).
Su diseo es muy parecido al anterior, aunque en este caso no existe puntero implcito this y
debemos referenciar el objeto Vector directamente:
Vector operator* (int i, Vector v) {
Vector vr;
vr.x = v.x * i;
vr.y = v.y * i;
return vr;
}
Su implementacin ( L.38) hace posible expresiones como:
mV1[2] = 5 * mV1[0]; // M.11:

6 Una vez introducidas todas las modificaciones anteriores en la versin bsica , el diseo
resultante es el siguiente:
#include <iostream>
using namespace std;

class Vector { // definicin de clase Vector
public: int x, y;
Vector& operator= (const Vector& v) { // L.6: asignacin V = V
x = v.x; y = v.y; return *this;
}
Vector operator* (int i) { // L.9: Producto V * int
Vector vr;
vr.x = x * i; vr.y = y * i;
return vr;
}
void showV();
friend Vector operator* (int, Vector); // L.15: Producto int * V
};
void Vector::showV() { cout << "X = " << x << "; Y = " << y << endl; }

class mVector { // definicin de clase mVector
int dimension; // L.20:
public:
Vector* mVptr;
mVector(int n = 1) { // constructor por defecto
dimension = n;
mVptr = new Vector[dimension]; // L.25:
}
~mVector() { // destructor
delete [] mVptr;
}
Vector& operator[](int i) { return mVptr[i]; }
void showmem (int); // L.31:
};

void mVector::showmem (int i) { // L.34:
if((i >= 0) && (i <= dimension)) mVptr[i].showV();
else cout << "Argumento incorrecto! pruebe otra vez" << endl;
}
Vector operator* (int i, Vector v) { // L.38:
Vector vr;
vr.x = v.x * i; vr.y = v.y * i;
return vr;
}

void main() { // =====================
mVector mV1(5); // M.1:
mV1[0].x = 2; mV1[0].y = 3;
mV1.showmem(0);

Vector v1;
v1 = mV1[0]; // M.6:
v1.showV();
mV1[4] = mV1[0] * 5; // M.8:
mV1.showmem(4);

mV1[2] = 5 * mV1[0]; // M.11:
mV1.showmem(2);
}
Salida:
X = 2; Y = 3
X = 2; Y = 3
X = 10; Y = 15
X = 10; Y = 15
Comentario
En un programa real, se debera implementar un mecanismo de control de excepciones que
pudiera controlar la posibilidad de que el operadornew del constructor (L.25) fuese incapaz de
crear el objeto ( 4.9.20). Es decir, controlar que operaciones como la de M.1 concluyen con
xito.
Para manejar convenientemente los lmites incluimos en mVector el miembro dimension (L.20);
su valor es iniciado por el constructor (L.24 ) y acompaa a cada instancia. El efecto es que es
posible implementar la interfaz de la clase de forma que el usuario no pueda acceder un elemento
fuera del espacio de la matriz.
Para facilitar la lectura incluimos en la mVector el mtodo showmem (L.31) que muestra los
componentes de un elemento de la matriz. Este mtodo utiliza el miembro dimension para
verificar que no pretendemos acceder a un elemento fuera de los lmites del objeto previamente
creado.
4.9.18e Sobrecarga del operador ->
1 Antecedentes
El selector indirecto de miembro -> ( 4.9.16) es un operador binario [1] que permite acceder a
miembros de objetos cuando se dispone de punteros a la clase correspondiente. Una expresin del
tipo Cpt->membr representa el miembro de identificador membr de la clase Cl siempre
que Cpt sea un puntero a dicha clase. Ejemplo:
class Cl {
public: int x;
} c1, *ClPt = &c1;
...
ClPt->x = 10;

Sabemos que la expresin ClPt->x exige que el primer operando Clpt sea un puntero a la
clase, y que el segundo x, sea el identificador de uno de sus miembros.
2 Sinopsis
La gramtica C++ permite definir una funcin miembro operator-> que puede ser invocada con la
sintaxis del operador selector indirecto de miembro ->. Por ejemplo, siendo obj una instancia de
la clase Cl para la que se define la funcin operator->, y membr un miembro de la misma [2], la
expresin:
obj->membr;
es transformada por el compilador en una invocacin del tipo:
( obj.operator->() )->membr;

La parte entre parntesis obj.operator->(), representa la invocacin del mtodo operator-
> sobre el objeto obj. Puesto que el valor devuelto por la funcin ser considerado a su vez el
primer operando de -> aplicado a membr, la funcin operator-> debe devolver un puntero a un
objeto de la clase sobre el que se pueda aplicar el operador ->. Es decir, su diseo debe tener el
siguiente aspecto:
class Vector {
...
Vector* operator-> () {
...
return this;
}
};
Observe que el puntero que se obtiene como resultado de la invocacin obj.operator->() no
depende de la naturaleza del operando membr. Por esta razn se considera a veces que operator-
> es un operador unario posfijo ( 4.9). Lo que significa que una expresin como
v.operator->();
tiene sentido y devuelve un puntero al objeto:
Vector v1;
Vector* vptr;
vptr = v1.operator->(); // Ok! vptr seala ahora a v1
3 Condiciones
Para conseguir este comportamiento el compilador impone ciertas limitaciones, de forma que la
funcin operator-> solo puede ser sobrecargada cumpliendo simultneamente las siguientes
condiciones:
a. Ser una funcin-miembro no esttica (que incluya el puntero this como argumento
implcito 4.11.6).
b. Ser una funcin-miembro que no acepte argumentos.
Ejemplo:
class Cl {
...
friend Cl* operator->(); // Error debe ser una funcin-miembro
Cl* operator->(int i) {/*...*/} // Error no acepta argumentos
Cl* operator->() {/*...*/} // Ok.
};
4 El operador -> no puede ser sobrecargado
Aunque esta afirmacin puede parecer escandalosa, ya que est en contradiccin con el ttulo del
captulo. Y adems, en cualquier bibliografa que se consulte, la descripcin de la
funcin operator-> se encuentra siempre en el captulo dedicado a la sobrecarga de operadores
[3]. Sin embargo, no se trata de una verdadera sobrecarga del selector indirecto ->. Al menos, no
en el sentido en que este mecanismo funciona con el resto de operadores.
Observe que en realidad, el compilador se limita a sustituir el primer operando de la
expresin obj->membr por la invocacin a una funcin-miembro, y una posterior utilizacin del
resultado como primer operando de la versin global del operador, mientras el segundo operando
se mantiene invariable. Adems, en dicha expresin (invocacin de la funcin-miembro), el
primer operando debe ser necesariamente un objeto (instancia) de la clase y no un
puntero Cl* como exige el uso regular del selector ->.

4.1 Este comportamiento, distinto de aquellos casos en que la versin global del operador es
sustituida "realmente" por la versin sobrecargada, puede verificarse con un sencillo ejemplo:
#include <iostream>
using namespace std;

class Vector {
public:
int x, y;
bool operator== (Vector v) { // L6: sobrecarga operador ==
cout << "Invocada funcion operator==() " << endl;
return ((v.x == x) && (v.y == y))? true: false;
}
Vector* operator-> () { // L10: sobrecarga? operador ->
cout << "Invocada funcion operator->() " << endl;
return this;
}
};

void main() { // =====================
Vector v1 = {2, 1}, v2 = {3, 0};
Vector* vptr = &v1;

cout << ( ( v1 == v2 )? "Iguales" : "Distintos" ) << endl; // M.4
cout << "v1.x == " << vptr->x << endl; // M.5
}
Salida:
Invocada funcion operator==()
Distintos
v1.x == 2
Comentario
Como puede verse, la utilizacin del operador de identidad == en M.4, provoca la utilizacin de la
versin sobrecargada ( 4.9.18b1). As mismo, la ausencia de la definicin de este operador
(L.6), habra producido un error de compilacin al tratar de utilizarlo en M.4:
'operator==' not implemented in type 'Vector' for arguments of the same
type in ...
Esto significa lisa y llanamente que el compilador no proporciona una versin por defecto de este
operador (de identidad) para los objetos de la clase Vector.
En cambio, la utilizacin de la (supuesta) versin sobrecargada del selector indirecto de miembro -
> (M.5), no produce la invocacin automtica de la misma como ocurri en el caso de la
identidad. En realidad, ante expresiones del tipo vptr->x como en M.5, el compilador sigue
utilizando la versin global (por defecto) del operador.

4.2 Si en el ejemplo anterior sustituimos las lneas M.4/5 por:
cout << "v1.x == " << v1->x << endl; // a
cout << "v1.x == " << v1.operator->()->x << endl; // b
cout << "v1.x == " << ( *v1.operator->() ).x << endl; // c
La salida indica que estas tres formas s implican la invocacin de la funcin operator->
Invocada funcion operator->()
v1.x == 2
Invocada funcion operator->()
v1.x == 2
Invocada funcion operator->()
v1.x == 2
Ya sabemos que le forma a es transformada por el compilador en la forma b, por lo que en
realidad se trata de tres invocaciones explcitas a la funcin operator->. Observe
que a, b y c son equivalentes, y representan variaciones sintcticas para referirse al
elemento v1.x.
5 Punteros inteligentes
Debemos resaltar que en el programa anterior disponemos de dos formas de acceso indirecto a los
miembros del objeto v1:
884

Potrebbero piacerti anche