Você está na página 1de 34

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:
cout << v1->x; // a
cout << vptr->x; // d

Hemos indicado que ambas utilizan la versin global del selector -> sobre el miembro x como
segundo operando. Pero existe una diferencia crucial: la forma a permite introducir una funcin
previa, representada por operator->( ), lo que abre todo un mundo de posibilidades.
En realidad, este comportamiento atpico de la funcin operator->( ), que hemos visto se aparta
del resto de operadores, no es arbitraria. Representa la puerta de acceso a lo que se
denominan punteros inteligentes; objetos que actan como punteros, pero que adems pueden
realizar alguna accin previa cada vez que un objeto es accedido a travs de ellos. Habida cuenta
que esta accin previa puede ser cualquiera (todo lo que pueda hacer una funcin), los punteros
inteligentes permiten tcnicas de programacin muy interesantes.
La idea puede ser concretada en tres formas bsicas que comentamos separadamente:
1. Incluir la funcin operator-> en la definicin de la clase
2. Incluir la funcin operator-> en una clase independiente
3. Incluir la funcin oprator-> en una clase anidada

5.1 Incluir la funcin operator->( ) en la definicin de la clase (es el caso del ejemplo anterior):
class Vector {
public: int x, y;
Vector* operator-> () {
/* funcionalidad adicional requerida */
return this;
}
};
Como se ha visto, este diseo permite que los objetos de la clase Vector puedan ser accedidos
indirectamente:
Vector v1;
v1->x = 2; v1->y = 4;

5.2 Incluir la funcin operator->( ) en una clase Vptr independiente:
class Vector {
...
};

class Vptr {
...
Vector* vpt;
Vector* operator->() {
/* funcionalidad adicional requerida */
return vpt;
}
};

En este caso los objetos Vptr pueden ser utilizados para acceder a los de clase Vector, de forma
parecida a como se utilizan los punteros. Lo ilustramos con un ejemplo compilable:
#include <iostream>
using namespace std;

class Vector { public: int x, y; };

class Vptr {
public:
class Vector* vpt;
Vector* operator->() {
cout << "Acceso a vector (" << vpt->x << ", " << vpt->y << ")" <<
endl;
return vpt;
}
};

void main() { // =====================
Vector v1 = {2, 1}; // objeto tipo vector inicializado
Vptr vpt = { &v1 }; // objeto Vptr que seala a v1
cout << "v1.x == " << vpt->x << endl;
vpt->y = 4;
cout << "v1.y == " << vpt->y << endl;
}
Salida:
Acceso a vector (2, 1)
v1.x == 2
Acceso a vector (2, 1)
Acceso a vector (2, 4)
v1.y == 4

5.3 Incluir la funcin operator->( ) en una clase Vptr contenida (anidada) en Vector:
class Vector {
...
class Vptr {
...
Vector* operator->() {
/* funcionalidad adicional requerida */
return vpt;
}
};
};

Si los objetos Vptr no tienen sentido como entes independientes de los objetos Vector, o no
pueden ser utilizados para otras clases, esta disposicin tambin puede ser vlida y en cierta
forma equivalente al diseo anterior.
Veamos este diseo en un ejemplo concreto:
#include <iostream>
using namespace std;

class Vector {
public: int x, y;
class Vptr {
public:
Vector* vpt;
Vector* operator-> () {
cout << "Accedido vector (" << vpt->x << ", " << vpt->y << ")" <<
endl;
return vpt;
}
} p1;
};

void main() { // ==============
Vector v1 = {1, 2};
v1.p1.vpt = &v1; // M.2
cout << "v1.x == " << v1.p1->x << endl; // M.3
v1.p1->y = 4;
cout << "v1.y == " << v1.p1->y << endl;
}
Como puede suponerse, la salida que se obtiene es idntica a la del ejemplo anterior.
Observe en M.2 la inicializacin del puntero vpt; un miembro del objeto p1. (miembro a su vez del
objeto v1). Esta circunstancia, miembros de objetos que pueden tener a su vez propiedades y
mtodos, es caracterstica de las clases cuyos miembros son a su vez instancias de otras clases.
En este caso, de la clase Vptr definida dentro de Vector.
Observe tambin que en M.3 y siguientes se invoca el mtodo operator-> del miembro p1 del
objeto v1.

5.3.2 Aunque la versin anterior es totalmente funcional, su diseo nos obliga a inicializar el
puntero vpt para cada instancia de la clase Vector(como se ha hecho en M.2). En el ejemplo que
sigue refinamos ligeramente el diseo anterior aadindole un constructor, al objeto de evitar tener
que realizar esta inicializacin cada vez.
#include <iostream>
using namespace std;

class Vector {
public: int x, y;
class Vptr {
public:
Vector* vpt;
Vector* operator-> () {
cout << "Accedido vector (" << vpt->x << ", " << vpt->y << ")" <<
endl;
return vpt;
}
} p1;
Vector (int i = 0, int j = 0) { // L.14: constructor
x = i; y = j;
p1.vpt = this;
}
};

void main() { // ================
Vector v1 = Vector(2, 1); // M.1
// v1.p1.vpt = &v1; ya no es necesaria M.2
cout << "v1.x == " << v1.p1->x << endl;
v1.p1->y = 4;
cout << "v1.y == " << v1.p1->y << endl;
}
La salida es tambin idntica a la de los dos anteriores. El cambio introducido se limita casi
exclusivamente a la inclusin de un constructor explcito (L.14) para la clase Vector.
Como puede comprobarse, la inicializacin M.2 ya no es necesaria; se ha encomendado esta
funcin al constructor. Ntese la utilizacin explcita del puntero this ( 4.11.6) para este
cometido.
Observe tambin que la existencia de un constructor explcito nos ha obligado a modificar
ligeramente la sintaxis de la sentencia M.1 en la que creamos e inicializamos el objeto v1 (
4.11.2d3).
En cualquier caso, cualquiera que sea el diseo adoptado para la "sobrecarga" del selector
indirecto, debemos conservar la idea central: estos punteros inteligentes permiten la utilizacin de
un cdigo cuya ejecucin es previa al acceso al objeto. En los ejemplos anteriores dicho cdigo se
ha concretado en una salida mostrando el estado actual (componentes) del vector.
4.9.18f Sobrecarga del operador de invocacin de funcin
1 Antecedentes:
La invocacin de funciones ( 4.4.6) en C++ tiene la siguiente sintaxis general:
expresin-postfija ( <lista-de-expresiones> );

1.1 En su utilizacin normal, expresin-postfija es el nombre de una funcin, un puntero a
funcin o la referencia a una funcin. Por ejemplo:
float sum(int i, int j) {
float s = i + j;
cout << "La suma es: " << s << endl;
return s;
}
...
float (*fptr)(int, int) = sum; // definicin de puntero-a-funcin
float (&fref)(int, int) = sum; // definicin de referencia-a-funcin
int x = 2, y = 5;
sum(x*2, y); // Ok. invocacin
fptr(x*2, y); // Ok. invocacin
fref(x*2, y); // Ok. invocacin

1.2 Cuando se utiliza con funciones-miembro (mtodos), expresin-postfija es el nombre
de un mtodo, una expresin de puntero-a-clase (utilizado para seleccionar un mtodo), o de
puntero-a-miembro. Por ejemplo:
class Vector { // una clase cualquiera
float x, y;
public: void getm(int i) { // funcin-miembro (mtodo)
cout << "Vector: (" << x * i << ", " << y * i << ") " << endl;
}
};
...
Vector v1; // Objeto
Vector* vptr = &v1; // definicin de puntero-a-clase
void (Vector::* vmptr) (int) // definicin de puntero-a-miembro
= &Vector::getm;
int x = 2;
v1.getm(x); // Ok. invocacin del mtodo
vptr->getm(x); // Ok. dem.
(v1.*vmptr)(x); // Ok. dem.

En este contexto nos referimos al parntesis ( ) como operador de invocacin de funcin (
4.9.16), aunque sabemos que tiene otros usos en el lenguaje: servir de signo de puntuacin (
3.2.6) y delimitador en algunas expresiones. Por ejemplo, las expresiones con coma (
4.10.5).
2 Sinopsis:
La gramtica C++ permite definir una funcin-miembro no esttica operator( ) cuya definicin sea
del tipo [1]:
valor-devuelto operator( )( <lista-de-argumentos> ) { /* definicin */
} // 2a
En este caso, las instancias de clases en que se han definido estos mtodos, presentan una
curiosa peculiaridad sintctica: que sus mtodosoperator() pueden ser invocados utilizando
directamente el identificador del objeto como expresin-postfija es decir:
obj( <lista-de-argumentos> );
siendo obj una instancia de la clase Cl para la que se define la funcin operator( ). Por ejemplo:
class Cl {
public:
...
void operator()(int x) { /* definicin */ }
...
};

...
Cl obj;
obj(5); // Ok!!
Cuando se utiliza esta notacin, el compilador transforma la expresin anterior en una invocacin
a operator( ) en la forma cannica:
obj.operator()( <lista-de-argumentos> ); // 2b
Observe que se trata simplemente de la invocacin de una funcin-miembro sobre el objeto obj, y
que nada impide que sea invocada directamente al modo tradicional con la sintaxis cannica
2b . Es decir, utilizando explcitamente la sustitucin realizada por el compilador.
No confundir la expresin anterior (2a ) con la utilizacin de operator( ) como operador de
conversin ( 4.9.18k), donde se utiliza sin especificacin del valor devuelto y sin que pueda
aceptar ningn tipo de parmetro:
operator( ){ /* valor devuelto */ } // 2c
Recordemos que operator( ) puede aparecer con dos significados en el interior de una clase:
class C {
valor-devuelto operator()(argumentos); // operador de invocacin a
funcin
operator() { /* ... */ } // operador de conversin
...
};

2.1 Lo ilustramos con un ejemplo:
#include <iostream>
using namespace std;

class Vector {
public:
float x, y;
void operator()() { // funcin-operador
cout << "Vector: (" << x << ", " << y << ") " << endl;
}
};

void main () { // =================
Vector v1 = {2, 3};
v1(); // Ok. invocacin de v1.operator()
v1.operator()(); // Ok. invocacin clsica
}
Salida
Vector: (2, 3)
Vector: (2, 3)
3 El operador de invocacin de funcin no es sobrecargable
Respecto a la "sobrecarga" del operador de invocacin de funcin ( ), podemos decir algo anlogo
a lo indicado para la sobrecarga del selector indirecto ->; ( 4.9.18e): a pesar de que en la
literatura sobre el tema, la descripcin de la funcin operator( ) se encuentra siempre en el
captulo dedicado a la sobrecarga de operadores, en realidad no se trata de tal sobrecarga. Al
menos no en un sentido homogneo al empleado con el resto de opradores. Hemos visto que se
trata de una mera curiosidad sintctica; una forma algo extraa de invocacin de determinadas
funciones-miembro (cuando estas funciones responden a un nombre especial).
Al hilo de lo anterior, y dado que el identificador operator( ) es nico, resulta evidente que si se
definen varias de estas funciones, se aplicar la congruencia estndar de argumentos ( 4.4.1a)
para resolver cualquier ambigedad. Por ejemplo:
#include <iostream>
using namespace std;

class Vector {
public: float x, y;
void operator()() { // L.6 Versin-1
cout << "Vector: (" << x << ", " << y << ") " << endl;
}
void operator()(int i) { // L.9 Versin-2
cout << "Coordenadas: (" << x << ", " << y << ") " << endl;
}
};

void main () { // ============
Vector v1 = {2, 3};
v1(); // Ok. invoca versin-1 b
v1(1); // Ok. invoca versin-2 c
}
Salida:
Vector: (2, 3)
Coordenadas: (2, 3)
Comentario
Observe que en L.9, el argumento (int) de la segunda definicin se ha utilizado exclusivamente
para permitir al compilador distinguir entre ambas. Esta tcnica ya la hemos visto en la sobrecarga
de los post-operadores incremento y decremento ( 4.9.18c).
4 Objetos-funcin
Lo indicado hasta aqu podra parecer un mero capricho sintctico del creador del lenguaje; una
forma particular de invocacin de ciertas funciones-miembro (de nombre especial), que presentan
la singularidad de permitir utilizar objetos como si fuesen funciones (caso de las expresiones b y
c ), pero que no tienen una justificacin objetiva, ya que no resuelve un problema que no pueda
ser resuelto con alguno de los recursos existentes en el lenguaje.
Precisamente, en razn de que pueden ser utilizadas como funciones, las instancias de clases
para las que se han definido funciones operator( ), reciben indistintamente el nombre de objetos-
funcin, funciones-objeto [2] o functor, y algn autor ha definido a estas entidades como "datos
ejecutables" [3].
En realidad, como ocurre con otros detalles de su diseo, este aparente capricho sintctico
encierra un mundo de sutilezas. Su importancia y razn de ser estriban en que permite escribir
cdigo que realiza operaciones complejas a travs de argumentos de funciones ( 5.1.3a1).
Precisamente la Librera Estndar de Plantillas C++ (STL 5.1) o sus extensiones, como las
libreras Boost, donde se encuentran algunos de los conceptos y algoritmos ms sofisticados que
haya construido hasta el momento la ingeniera de software, utiliza con profusin este tipo de
recursos.
Recuerde que los objetos (instancias de clases) pueden ser pasados como argumentos de
funciones y que sus mtodos pueden acceder a las propiedades de la clase, de forma que pasar
un objeto-funcin como argumento, equivale a pasar a la funcin ms informacin de la que
supondra un escalar. Esta circunstancia tiene muchas aplicaciones. Por ejemplo, la nueva versin
del Estndar permitir crear en una aplicacin un hilo ("thread") de ejecucin mediante una
expresin del tipo:
void work_to_do(); // L.1
std::thread newThread (work_to_do); // L.2
La funcin work_to_do() define el proceso que se ejecutar en el nuevo hilo representado por el
objeto newThread de L.2. Sin embargo, el constructor de la clase std::trhead exige como
argumento un puntero a funcin que no recibe argumentos y devuelve void. De forma que no es
posible pasar en el constructor ninguna informacin adicional sobre detalles de la tarea a realizar y
es en este punto, donde las caractersticas de C++ vienen al rescate, porque al igual que muchos
otros algoritmos de la Librera Estndar C++, el argumento no tiene porqu ser necesariamente
una funcin ordinaria; tambin puede ser un objeto-funcin, as que el diseo podra ser como
sigue:
class Work_to_do {
public:
// miembros que representan particularidades del proceso
int a_, b_;
// miembro que representa el resultado del proceso
int& c_;
// constructor que permite fijar las caractersticas
Work_to_do (int a, int b, int& c) : a_(a), b_(b), c_(c) {}

// operador de invocacin a funcin
void operator()() { c_ = a_ + b_; }
};
...
int r;
std::thread newThread (Work_to_do(1,2,r));
Observe que el argumento Work_to_do(x,y,z) es una llamada al constructor de la clase, que
genera un objeto-funcin; que a su vez, es pasado al constructor de la clase std::trhead para
construir el objeto newThread (que representa el nuevo hilo). Una vez concluida la tarea, el
resultado lo obtenemos en r.
4.1 Unin de argumentos
Observe que en el ejemplo anterior, el recurso ha consistido en empaquetar los argumentos
involucrados en un objeto-funcin. Esta tcnica, conocida como unin o empaquetado de
argumentos ("argument binding") es ampliamente utilizada, aunque tal como la hemos
presentado, tiene el inconveniente de que hay que preparar manualmente la clase adecuada. Sin
embargo, si como suele ser frecuente [4] la situacin se repite, es posible automatizarla utilizando
una plantilla.
Supongamos que el proceso que deba ejecutar la hebra ("thread") pueda ser definida
genricamente mediante una funcin del tipo
void work_to_do (A a, B b, C& c);
En la que los objetos a y b representan los datos particulares y c la variable en la que se obtiene
el resultado. En estas circunstancias, es posible definir una clase genrica ( 4.12.2) tal como:
template <typename A, typename B, typename C> class bind {
public:
A a_;
B b_;
C& c_;
void (*pf)(A,B,C&);
bind (void (*p)(A,B,C&), A a, B b, C& c)
: pf(p), a_(a), b_(b), c_(c) {}

void operator()() { pf(a_, b_, c_); }
};
Suponiendo que tenemos definida la funcin que realiza el proceso en un caso concreto:
void process1 (int a, char b, float& c) {
/* proceso a realizar por la hebra
el resultado es situado en c */
...
}
Para lanzar una hebra que realizara esa tarea, solo tendramos que incluir un par de lenas en
nuestro cdigo:
float f;
std:thread tread1 (bind (process1, 2, 'c', f));

4.9.18g Sobrecarga de operadores lgicos
1 Sinopsis:
Recordemos ( 4.9.8) que los operadores lgicos suelen ser representados por sus nombres en
ingls (maysculas): AND (&&); OR (||) y NOT (!). Los dos primeros son binarios, mientras que el
ltimo es unario, lo que significa que AND y OR aceptan dos argumentos, mientras que la negacin
NOT, acepta solo uno. Recordemos tambin que los operandos de las versiones globales de estos
operadores son convertidos a un tipobool, y que el resultado es tambin un tipo bool de valor
cierto/falso (true/false).
Cualquier intento de aplicar estos operadores a tipos abstractos ( 2.2) genera un error de
compilacin recordndonos que la operacin solo est definida para los tipos bsicos
(preconstruidos en el lenguaje). La solucin en estos casos es sobrecargar adecuadamente estos
operadores para los miembros de la clase, lo que puede realizarse mediante los procedimientos
estndar ya sealados para operadores unarios ( 4.9.18c) y binarios ( 4.9.18b).
2 Permanencia de las leyes formales
Antes de exponer algunos ejemplos, recordemos los preceptos que hemos denominado de
permanencia de las leyes formales ( 4.9.8) que son especialmente pertinentes en estos casos
de sobrecarga.
Hemos sealado que la sobrecarga permite al programador una gran libertad, de forma que puede
cambiar totalmente la funcionalidad del operador respecto a la que tiene con los tipos bsicos. Por
ejemplo, podemos definir el operador AND entre miembros c1 y c2 de una clase C de forma que
devuelva un valor de cualquier tipo en vez de un booleano, aunque lo lgico sera que fuese
un bool, con objeto que el resultado de estas operaciones fuese el que se espera intuitivamente.
Adems de esto, considere que los tres operadores estn relacionados desde el punto de vista
lgico, y deberan seguir estndolo de la misma forma en las versiones sobrecargadas.
Por ejemplo, si definimos la sobrecarga del operador de negacin lgica NOT de forma que para
los objetos c1 y c2 resulta:
!c1 == false
!c2 == false
Deberamos definir la sobrecarga del operador AND de forma que
(c1 && c2) == true
De lo contrario estaramos construyendo para los objetos de tipo C una lgica bastante difcil de
comprender a una mente acostumbrada al razonamiento estndar con los tipos bsicos del
lenguaje.
En este captulo trataremos de demostrar que si se quiere mantener una lgica coherente, la
sobrecarga de los tres operadores lgicos AND, OR y NOT para tipos de una clase C, puede ser
sustituida por una conversin de usuario mediante una funcin de conversin operator
bool() adecuada ( 4.9.18k).
3 Sobrecarga del operador NOT
El operador NOT de negacin lgica ( ! ) est relacionado con su contrario (que no tiene nombre ni
representacin). Para explicar el significado de esta afirmacin, supongamos un objeto c de una
clase C. Segn hemos sealado, un intento de utilizarlo, por ejemplo, la expresin:
if (!c) { /* ... */ }
genera un error de compilacin: 'operator!' not implemented in type 'C'..., en el
que se indica que el operador NOT de negacin lgica no est definido para objetos de la clase.
Sin embargo, podemos observar que un intento anlogo sin el operador:
if (c) { /* ... */ }
tambin produce un error, aunque en este caso la indicacin es ms ambiga: Illegal
structure operation in function...(Borland) o: conditional expression of type
'class C' is illegal (Visual C++). En este ltimo caso el compilador est indicando que no
sabe como convertir la expresin entre parntesis (c) a un tipo bool. Recuerde que la
sentencia if(<condicion>)... espera recibir una expresin <condicion> que se resuelva en
un bool ( 4.10.2).

3.1 Ambos inconvenientes pueden resolverse adoptando las medidas pertinentes. El primero
sobrecargando el operador NOT para objetos de la clase. El segundo proporcionando una
conversin de usuario que permita al compilador transformar un tipo C en un bool ( 4.9.18k).
Por ejemplo, supongamos una clase V2D para contener puntos de un plano definidos por sus
coordenadas cartesianas. Para ciertas operaciones lgicas con estos objetos, consideramos
"ciertos" los puntos que pertenecen al primer cuadrante y "falsos" todos los dems. Un posible
diseo sera el siguiente:
class V2D { // clase de puntos en el plano
float x, y;
public:
V2D(float i=0, float j=0): x(i), y(j) {} // constructor
bool operator!() { // sobrecarga del operador NOT
return ((x > 0 && y > 0) ? false : true );
}
operator bool() { // conversin de usuario
return ((x > 0 && y > 0) ? true : false );
}
};
...
void func () {
V2D p1(0,2);
V2D p2(-1.1, 2);
V2D p3 = Vector2D(1,2);
V2D p4(1, -3);

if (p1) cout << "p1 Ok.";
if (p2) cout << "p2 Ok.";
if (p3) cout << "p3 Ok.";
if (p4) cout << "p4 Ok."; // p3 Ok.

if (!p1) cout << "p1 Not Ok."; // p1 Not Ok.
if (!p2) cout << "p2 Not Ok."; // p2 Not Ok.
if (!p3) cout << "p3 Not Ok."; // p3 Not Ok.
if (!p4) cout << "p4 Not Ok.";
}
Observe que la definicin de operator bool debe ser congruente con la definicin de operator!, de
forma que un objeto no pueda ser cierto y falso al mismo tiempo.
3.2 Observe tambin que una vez definida la funcin de conversin operator bool, la de
negacin no es realmente necesaria. En efecto: la definicin
class V2D { // clase de puntos en el plano
float x, y;
public:
V2D(float i=0, float j=0): x(i), y(j) {} // constructor
operator bool() { // conversin de usuario
return ((x > 0 && y > 0) ? true : false );
}
};
produce exactamente las mismas salidas que la anterior. La razn es que en las expresiones (!
p) el objeto p es convertido a tipo bool por accin de la versin global del propio operador NOT.
Esta conversin se realiza mediante una invocacin del tipo p.operator bool().

3.3 Un ltimo truco podra permitirnos la operatoria inversa: obtener los valores (p) mediante
una doble negacin (!!p). En cuyo caso podramos eliminar la conversin de usuario operator
bool, dejando la sobrecarga del operador de negacin operator!:
class V2D { // clase de puntos en el plano
float x, y;
public:
V2D(float i=0, float j=0): x(i), y(j) {} // constructor
bool operator!() { // sobrecarga del operador NOT
return ((x > 0 && y > 0) ? false : true );
}
};
...
void func () {
V2D p1(0,2);
V2D p2(-1.1, 2);
V2D p3 = Vector2D(1,2);
V2D p4(1, -3);

if (!!p1) cout << "p1 Ok.";
if (!!p2) cout << "p2 Ok.";
if (!!p3) cout << "p3 Ok.";
if (!!p4) cout << "p4 Ok."; // p3 Ok.

if (!p1) cout << "p1 Not Ok."; // p1 Not Ok.
if (!p2) cout << "p2 Not Ok."; // p2 Not Ok.
if (!p3) cout << "p3 Not Ok."; // p3 Not Ok.
if (!p4) cout << "p4 Not Ok.";
}

4 Sobrecarga de peradores AND y OR
Es significativo que si, como en el caso anterior 3.2 , se dispone de una conversin de usuario
que garantice la conversin de un objeto c de tipo C a tipo bool, entonces no es realmente
necesario sobrecargar los operadores AND ni OR para poder utilizarlos. Por ejemplo, suponiendo
la definicin (3.2) y los vectores (3.3) anteriores, las sentencias:
if (p3 && p1) cout << "p3 y p1 Ok.";
else cout << "p3 y p1 NOT ok.";
if (p3 || p1) cout << "p3 o p1 Ok.";
else cout << "p3 o p1 NOT Ok.";
Producen las siguientes salidas:
p3 y p1 NOT ok.
p3 o p1 Ok.
La razn es la sealada en el caso del operador NOT (3.2 ). Si existe posibilidad de conversin
de los operandos a tipos bool, entonces el compilador utiliza las versiones globales de ambos
operadores una vez realizada la conversin correspondiente.

4.1 Si de todos modos es preciso sobrecargar alguno de estos operadores, el procedimiento es
el mismo que con cualquier operador binario ( 4.9.18b):
a. Declarando una funcin miembro no esttica que acepte un argumento
b. Declarando una funcin externa (generalmente friend) que acepte dos argumentos.

Como ejemplo, procederemos a la sobrecarga de los operadores AND y OR para la clase V2D ya
mencionada, que utilizamos para contener puntos de un plano. Las operaciones mantendrn
coherencia con los principios utilizados al sobrecargar el operador NOT (3.1 ). Recordemos
que para las operaciones lgicas con estos objetos, consideramos "ciertos" los puntos que
pertenecen al primer cuadrante y "falsos" todos los dems.
La sobrecarga de AND se realiza mediante el procedimiento a (una funcin miembro no esttica);
para OR se utiliza el procedimiento b (una funcin externa que acepte dos argumentos). El diseo
es el siguiente:
#include <iostream>
using namespace std;

class V2D {
public:
float x, y;
V2D(float i=0, float j=0): x(i), y(j) { } // constructor
bool operator&&(V2D& p) { // funcin-operador
if (x > 0 && y > 0) {
if (p.x > 0 && p.y > 0) return true;
}
return false;
}
friend bool operator||(V2D&, V2D&);
};

bool operator||(V2D& p1, V2D& p2) { // funcin-operador
if (p1.x > 0 && p1.y > 0) return true;
if (p2.x > 0 && p2.y > 0) return true;
return false;
}

int main() { // =================
V2D p1(0,2);
V2D p2(-1.1, 2);
V2D p3 = V2D(1,2);
V2D p4(1, -3);

if (p3 && p1) cout << "p3 y p1 Ok.\n";
else cout << "p3 y p1 NOT ok.\n";
if (p3 || p1) cout << "p3 o p1 Ok.\n";
else cout << "p3 o p1 NOT Ok.\n";
}
Salida:
p3 y p1 NOT ok.
p3 o p1 Ok.
Comentario
Observe que las funciones operator&& y operator|| utilizan la versin global del operador AND
(&&). Observe tambin que en ambas definiciones se ha respetado la mecnica descrita en el
Estndar para las versiones globales. De forma que se devuelve un resultado tan pronto como se
tiene constancia de este, sin necesidad de terminar todas las comprobaciones que puedan estar
involucradas. Por ejemplo, en la definicin deoperator||, si se cumple la primera
comprobacin (p1.x > 0 && p1.y > 0), se devuelve directamente un resultado true, sin
necesidad de esperar a realizar la segunda.
4.9.18h Sobrecarga de Enumeraciones
1 Sinopsis
El hecho de referirnos a la sobrecarga de enumeraciones ( 4.7) podra parecer en
contradiccin con lo indicado al tratar de la sobrecarga en general ( 4.9.18): que "se refiere y
tiene aplicacin solo cuando los operandos son instancias de clases". Sin embargo, hay que tener
en cuenta que las enumeraciones son en realidad un tipo muy particular de estructuras (y por
ende, de clases), que como tales, gozan de muchas de las caractersticas de aquellas [1].
2 Sobrecarga
Es posible sobrecargar la mayora de los operadores para una enumeracin, pero dado que estas
no pueden tener funciones-miembro, no es posible sobrecargar aquellos que precisamente exigen
ser sobrecargados a travs de mtodos no estticos. Concretamente los operadores =, [ ], ( ) y -
> no pueden ser sobrecargados para un enum.
La consecuencia inmediata es aadir que la sobrecarga de enumeraciones debe realizarse a
travs de funciones ordinarias (no pertenecientes a clases), y que al menos uno de los argumentos
debe ser del tipo de la enumeracin que se sobrecarga.

3 Comprobar en el ejemplo que sigue la forma de sobrecargar los operadores de preincremento y
postincremento. Observe que en cierta forma, la enumeracin se comporta como una clase.
#include <iostream>
#using amespace std;

enum ESTACION { primavera, verano, otono, invierno };
ESTACION& operator++ (ESTACION& s) { // Preincremento ++@
s = ESTACION( (s + 1) % 4 ); // L.6
return s;
}
ESTACION operator++(ESTACION& s, int) { // Postincremento @++
ESTACION tmp = s;
switch (s) {
case primavera: s = verano; break;
case verano: s = otono; break;
case otono: s = invierno; break;
case invierno: s = primavera; break;
}
return tmp;
}

int main(void) { // =============
ESTACION est = otono;
cout << "La estacion es " << est << endl;
cout << "Preincrementar la estacion: "<< ++est << endl;
cout << "No cambiar si se usa el postincremento: " << est++ << endl;
cout << "Finalmente: " << est << endl;
}
Salida:
La estacion es 2
Preincrementar la estacion: 3
No cambiar si se usa el postincremento: 3
Finalmente: 0
Comentario
Por tratarse de la sobrecarga de operadores unarios como funciones externas, ambas funciones-
operador se han definido utilizando la forma que hemos denominado "b" ( 4.9.18c):
Preincremento: funcin que acepta un argumento operator++(ESTACION)
Posincremento: funcin acepta un objeto y un entero operator++(ESTACION, int)
El preincremento recibe el argumento por referencia (L.5), de forma que modifica el valor recibido.
Observe como en L.6 se efecta el calculo aritmtico, seguido de un modelado al tipo ESTACION, y
como en L.7 este valor es devuelto por referencia.
Observe como el postincremento devuelve un objeto "por valor", y que este objeto es una copia del
objeto inicial. Simultneamente la funcin modifica el valor del objeto recibido inicialmente (por
referencia).
4.9.18k Conversiones definidas por el usuario
El presente captulo es un claro ejemplo de un tpico difcil de clasificar.
Aunque podra haber encajado igualmente bien (o mal) en otros sitios, lo
hemos incluido en el epgrafe dedicado a la sobrecarga de operadores porque
en una de sus formas se refiere a la funcin-operador operator. Este es
tambin el criterio del Dr. Stroustrup en su obra TC++PL.
1 Prembulo
Recordemos que el lenguaje C++ dispone de una serie de mecanismos de conversin para los
tipos bsicos, que son utilizados automticamente en determinadas circunstancias. Son las
conversiones estndar ( 2.2.5). El lenguaje tambin permite que puedan realizarse conversiones
implcitas o explcitas para los tipos abstractos, aunque en este caso es el programador el que
debe adoptar las medidas pertinentes, razn por la cual se denominan conversiones definidas
por el usuario ("User-defined conversions").
Existen dos formas de definir estas ltimas conversiones: mediante constructores y mediante
operadores de conversin. Ambos tipos sern tratados en el presente captulo.
2 Conversiones de constructor
Hemos indicado ( 4.9.9) que el modelado de tipos est estrechamente relacionado con los
constructores de clases, y que la posibilidad de realizar un modelado de un objeto b de tipo B, a
otro tipo A distinto:
a = A(b);
depende de cmo est definido el constructor de la clase A. Para que esta conversin sea posible,
debe existir un constructor de conversin ( 4.11.2d1) que acepte un objeto tipo B como nico
argumento. Es decir, debe existir un mtodo:
A::A(B b) { /* detalle de la conversin */ }

En realidad, los constructores de conversin constituyen el soporte del mecanismo C++ de
modelado, de forma que la existencia de estos constructores es condicin necesaria y suficiente
para que pueda efectuarse este ltimo (el modelado). Por ejemplo:
class X {
public:
X(int); // constructor C-1
};
la mera existencia del constructor C-1 en la clase X, permite las siguientes asignaciones:
void f() {
X a = 1; // Ok. invocacin implcita a X(1)
X b(1); // Ok. invocacin implcita a X(1)
a = 2; // Ok. invocacin implcita a X(2)
a = (X) 2; // Ok. casting explcito (estlo tradicional)
a = static_cast<X>(2); // Ok. casting explcito (estilo C++)
}
Si eliminamos el constructor C-1 de la declaracin de la clase, las sentencias anteriores seran
errneas.
Nota: las tres ltimas sentencias implican en realidad dos operaciones: la creacin de un
objeto temporal tipoX conteniendo el Rvalue de la expresin y una asignacin posterior
utilizando el operador de asignacin implcito de la clase X.
2.1 Ejemplo
Para ilustrar la problemtica de este tipo de conversiones explcitas e implcitas, construiremos un
ejemplo ejecutable en el que creamos una clasePar que destinaremos a albergar los enteros
mltiplos de 2. Lo que pretendemos es poder utilizar los miembros de esta clase en todas las
circunstancias en que se podra utilizar un tipo bsico int suponiendo que su valor sea un nmero
par (divisible por 2).
El criterio para aceptar que un entero n puede ser miembro de la clase es que el resto de la
divisin n/2 sea cero (consideramos que el cero es par, puesto que 0/2 es 0).
El diseo bsico es el siguiente:
#include <iostream>
using namespace std;

class BadNumber {
int num; // miembro -privado por defecto-
public:
BadNumber(int n=0): num(n) {} // constructor explcito
int what() { return num; } // mtodo
};

class Par {
int val;
void verify(int n) { if (n % 2) throw BadNumber(n); }
public:
Par(int n=0) { // L.15: constructor de conversin
verify(n);
val = n;
cout << "Creado numero " << val << endl;
}
};

int main() { // =================
try {
Par p0; // M2: Creado numero 0
Par p2 = 2; // M3: Creado numero 2
Par p3 = 3; // M4: Error: numero 3 impar
Par p4 = (Par) 4; // M5: Creado numero 4
Par P6 = Par(6); // M6: Creado numero 6
}
catch (BadNumber& e) {
cout << "Error: numero " << e.what() << " impar." << endl;
}
}
Salida:
Las salidas producidas por las sentencias de asignacin contenidas en la funcin main, se han
incluido en los comentarios junto a las sentencias, aunque las repetimos aqu:
Creado numero 0
Creado numero 2
Error: numero 3 impar
Creado numero 4
Creado numero 6
Comentario
La clase BadNumber sirve para lanzar una excepcin en caso que se pretenda crear un
nmero Par invlido. El mtodo what nos devuelve dicho nmero.
La clase Par tiene un diseo muy simple: el miembro val almacena el nmero correspondiente. El
mtodo verify sirve para comprobar que el "valor" del objeto a crear cumple la condicin exigida
de ser par. En caso contrario lanza una excepcin ( 1.6) que ser recogida por el dispositivo try
... catch correspondiente de la funcin main.
Se ha dotado a la clase de un constructor por defecto que acepta un int como argumento. Si el
valor n suministrado pasa la verificacin correspondiente, se inicia el miembro val con el valor del
argumento n y se muestra en pantalla el nmero creado.
En los comentarios de las sentencias M2 a M6 se muestra la salida obtenida en cada caso. Puede
comprobarse que la existencia de un constructor de conversin como el definido en L.15 permite
los modelados implcitos en las sentencias M3 y M4 o explcitos (sentencias M5 y M6), donde las
constantes numricas utilizadas como Rvalues son convertidas a objetos de tipo Par.
Observe que no ha sido necesario definir el operador de asignacin = entre objetos Par. La versin
por defecto suministrada por el compilador ( 4.9.18a) resulta suficiente para las asignaciones
planteadas.
2.1 Aumentar la funcionalidad
La clase diseada cumple con su funcin de almacenar nmero pares. Tambin permite una
asignacin de tipo Par = int, y detecta cualquier intento de crear un objeto no vlido. Sin embargo,
dista mucho de poder ser utilizada con la misma generalidad que los objetos de tipo int. Por
ejemplo, las siguientes sentencias produciran un error de compilacin:
Par p3 = p2 + 2; // Error
Par p4 = p2 + p2; // Error
Par p8 = 4 + p4; // Error
La razn es que no estn definidas las operaciones correspondientes:
suma Par & int; suma Par & Par, y suma int & Par [1].
Para acercar nuestro diseo a la funcionalidad deseada agregamos los operadores
correspondientes. Seran los siguientes algoritmos:
Nota: en los ejemplos que siguen, pi es un objeto tipo Par, y n es un int.
2.1a Operador suma + entre tipos Par e int (resuelve situaciones del tipo pi + n):
operator+(int n) {
verify(n); // verificar que el operando n es adecuado
val += n;
}

2.1b Operador suma + para tipos Par (resuelve situaciones del tipo pi + pj):
Par operator+(const Par& p) {
return Par(val + p.val);
}
Observe que el valor a devolver por la funcin se ha obtenido mediante una explcita al constructor
utilizando el argumento adecuado. Aunque esta operacin no precisa de verificacin, de todas
formas el constructor la realiza.

2.1c Operador suma + entre tipos int y Par (resuelve situaciones del tipo n + pi):
Par operator+(int n, Par p) {
Par pt(n); // L1:
return pt + p; // L2:
}
La sentencia L1 crea un objeto automtico pt tipo Par y valor n, mediante una invocacin implcita
al constructor de la clase (el constructor se encarga de verificar que el valor n es correcto). En L2
se utiliza el operador suma entre objetos tipo Par, definido en el punto anterior (2.1b ), para
devolver el objeto resultante de la operacin.
Una definicin alternativa y ms eficiente, sera la siguiente:
Par operator+(int n, Par p) {
return Par(p.val + n);
}

Las dos primeras funciones-operador se integran como mtodos no estticos de clase; la ltima
como funcin externa. En el listado adjunto se muestra el diseo resultante despus de las
adiciones anteriores ( Listado-1). El nuevo diseo permite realizar las operaciones deseadas
(2.1b ) con los resultados que se indican:
Par p3 = p2 + 1; // -> Error: numero 1 impar.
Par p4 = p2 + p2; // -> Creado numero 4
Par p5 = 3 + p2; // -> Error: numero 3 impar.
Par p8 = 4 + p4; // -> Creado numero 4
// -> Creado numero 8
Observe que los resultados son los esperados; la doble salida generada por la ltima sentencia es
producida por el algoritmo 2.1c . La primera corresponde a la creacin del objeto
automtico pt. La segunda a la construccin del objeto a devolver realizada en L2.
2.2 Nuevas dificultades
No obstante lo anterior, una total libertad para la utilizacin conjunta de nuestro tipo Par con el
tipo int exigira muchas ms posibilidades. Por ejemplo:
Par p2 += 2;
Par p4 += p2;
Par p8 /= 2;
++p2;
etc.
A las anteriores habra que aadir todas las circunstancias en que la conversin deba realizarse en
sentido contrario (del tipo Par a int). Por ejemplo:
int x = p2;
int y = 3 + p2;
y += p2;
etc.
Con el diseo actual de la clase Par, todas estas sentencias producen un error de compilacin. Su
utilizacin exigira implementar toda una serie de versiones sobrecargadas de los operadores
correspondientes, lo que supone desde luego una buena cantidad de cdigo.

Você também pode gostar