de operadores en c++ tutorial: sobrecarga tabla de contenidosen esta versión no haremos uso de la...

23
1 Tutorial: Sobrecarga de Operadores en C++ Diego Bravo Estrada / AmericaTI.com 11 de Julio de 2006 Tabla de contenidos Intro .................................................................................................................... 1 Autoría y Copyright ..................................................................................... 1 Revisiones .................................................................................................. 2 Pros y Cons de la sobrecarga de operadores ............................................ 2 Álgebra de Polinomios ....................................................................................... 3 Versión Inicial ............................................................................................. 3 Introducción de Operadores ....................................................................... 5 Uso eficiente de referencias ....................................................................... 7 Sobrecarga del operador de "subscript" ................................................... 10 Conmutatividad ......................................................................................... 13 Optimizando la implementación: funciones friend ..................................... 15 Conversión de tipos .................................................................................. 16 Polinomios Especializados ............................................................................... 17 Un Template de Polinomios ............................................................................. 20 A. Bibliografía Recomendada ............................................................................. 23 Intro Este texto pretende ilustrar los aspectos más frecuentes de la programación en C+ + referida a la sobrecarga de operadores; asimismo se ha intentado aprovechar los ejemplos para demostrar superficialmente algunas otras características del lenguaje tales como las referencias, las excepciones, la herencia y los templates. En caso de hallar errores (en la última versión publicada) por favor no dude en contactarnos a fin de efectuar las correcciones pertinentes. Opiniones y comentarios también son bienvenidos. El código fuente de los ejemplos y versiones corregidas del documento se pueden descargar desde Americati.com. Autoría y Copyright Este documento tiene copyright (c) 2006 AmericaTI EIRL (www.americati.com.) Se otorga permiso para copiar, distribuir y/o modificar este documento bajo los térmi- nos de la "GNU Free Documentation License, Version 1.2", excepto en lo mencio-

Upload: others

Post on 06-Aug-2020

3 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: de Operadores en C++ Tutorial: Sobrecarga Tabla de contenidosEn esta versión no haremos uso de la sobrecarga de operadores, limitándonos a la creación de los polinomios y a la operación

1

Tutorial: Sobrecargade Operadores en C++

Diego Bravo Estrada / AmericaTI.com11 de Julio de 2006

Tabla de contenidosIntro .................................................................................................................... 1

Autoría y Copyright ..................................................................................... 1Revisiones .................................................................................................. 2Pros y Cons de la sobrecarga de operadores ............................................ 2

Álgebra de Polinomios ....................................................................................... 3Versión Inicial ............................................................................................. 3Introducción de Operadores ....................................................................... 5Uso eficiente de referencias ....................................................................... 7Sobrecarga del operador de "subscript" ................................................... 10Conmutatividad ......................................................................................... 13Optimizando la implementación: funciones friend ..................................... 15Conversión de tipos .................................................................................. 16

Polinomios Especializados ............................................................................... 17Un Template de Polinomios ............................................................................. 20A. Bibliografía Recomendada ............................................................................. 23

Intro

Este texto pretende ilustrar los aspectos más frecuentes de la programación en C++ referida a la sobrecarga de operadores; asimismo se ha intentado aprovechar losejemplos para demostrar superficialmente algunas otras características del lenguajetales como las referencias, las excepciones, la herencia y los templates.

En caso de hallar errores (en la última versión publicada) por favor no dude encontactarnos a fin de efectuar las correcciones pertinentes. Opiniones y comentariostambién son bienvenidos.

El código fuente de los ejemplos y versiones corregidas del documento se puedendescargar desde Americati.com.

Autoría y Copyright

Este documento tiene copyright (c) 2006 AmericaTI EIRL (www.americati.com.) Seotorga permiso para copiar, distribuir y/o modificar este documento bajo los térmi-nos de la "GNU Free Documentation License, Version 1.2", excepto en lo mencio-

Page 2: de Operadores en C++ Tutorial: Sobrecarga Tabla de contenidosEn esta versión no haremos uso de la sobrecarga de operadores, limitándonos a la creación de los polinomios y a la operación

Tutorial: Sobrecarga de Operadores en C++

2

nado en el siguiente párrafo. Esta licencia puede obtenerse en: http://www.gnu.org/licenses/fdl.txt

Si se desea crear un trabajo derivado o publicar este documento para cualquier pro-pósito, por favor contactarnos (vía nuestra página web) a fin de tener la oportunidadde proporcionar una versión más reciente. De no ser esto posible, la última versióndebería estar disponible en el sitio web AmericaTI.com. [http://www.americati.com]

Son bienvenidas todas las sugerencias y correcciones.

Este documento fue confeccionado con DocBook utilizando el preprocesador Qdkdisponible en SourceForge.net. [http://qdk.sourceforge.net]

Revisiones

• 1.1 2006-07-11 Correcciones en retorno de referencia a temporales

• 1.0 2006-06-15 Primera versión

Pros y Cons de la sobrecarga de operadores

Beneficios

Los objetos se comportan de manera similar a los tipos nativos del lenguaje. Estosimplifica la escritura de muchas expresiones en las que los objetos se relacionanentre sí.

En particular, casi todos los objetos de índole matemática se benefician con la so-brecarga de operadores. Por ejemplo, la librería de C++ proporciona una clase pararepresentar números complejos la cual implementa todas las operaciones básicasmediante operadores, consiguiendo así que este nuevo tipo luzca como cualquierotro tipo nativo tal como "int" o "double".

Desventajas

Es más trabajo para quien diseña y escribe la clase (mas no para quien la utiliza.)La sintaxis es un poco oscura y presenta diversos aspectos poco evidentes querequieren cuidado en su uso.

Para muchas clases y objetos los operadores no agregan mucha ventaja expresiva.Por ejemplo, si tenemos un objeto que representa algún repositorio de datos (lla-mémosle "database") y queremos registrar cierta información en éste (mediante unobjeto llamado "informacion"), podríamos sobrecargar el operador "+=" para indicarque la "informacion" está siendo agregada a "database":

Page 3: de Operadores en C++ Tutorial: Sobrecarga Tabla de contenidosEn esta versión no haremos uso de la sobrecarga de operadores, limitándonos a la creación de los polinomios y a la operación

Tutorial: Sobrecarga de Operadores en C++

3

database+=informacion;

El significado de esta expresión no es evidente, y requiere que documentemos ade-cuadamente el significado del operador "+=". Sin embargo, el "método clásico" síproporciona un significado que salta a la vista:

database.registrar(informacion);

Álgebra de Polinomios

En las siguientes secciones ilustraremos el uso de la sobrecarga de operadores me-diante objetos que representan polinomios cuyos coeficientes son números reales.

Versión Inicial

En esta versión no haremos uso de la sobrecarga de operadores, limitándonos ala creación de los polinomios y a la operación de adición. Los coeficientes seránalmacenados en un vector, por lo que el archivo cabecera debe incluir el header<vector>:

// polinomio.h#include <vector>

class Polinomio {public:int getGrado() const;double getCoeficiente(int grado) const;void setCoeficiente(int grado, double valor);void display() const;Polinomio suma(const Polinomio &pol) const;protected:std::vector<double> coeficiente;};

Tener en cuenta que no estamos proporcionando ningún constructor por lo que el"default constructor" se limitará a construir el vector "coeficiente" con cero ele-mentos.

La implementación de los métodos se muestra a continuación:

// polinomio.cc#include "polinomio.h"#include <iostream>

double Polinomio::getCoeficiente(int grado) const{if(grado<0 || grado>getGrado()) return 0;return coeficiente[grado];}

void Polinomio::setCoeficiente(int grado, double valor){if(grado<0) return;

Page 4: de Operadores en C++ Tutorial: Sobrecarga Tabla de contenidosEn esta versión no haremos uso de la sobrecarga de operadores, limitándonos a la creación de los polinomios y a la operación

Tutorial: Sobrecarga de Operadores en C++

4

while(grado>getGrado()) coeficiente.push_back(0);coeficiente[grado]=valor;}

int Polinomio::getGrado() const{return coeficiente.size()-1;}

void Polinomio::display() const{for(int z=getGrado();z>=0;z--) std::cout << coeficiente[z] << " ";}

Polinomio Polinomio::suma(const Polinomio &b) const{Polinomio r=*this;for(int z=0;z<=b.getGrado();z++) r.setCoeficiente(z,getCoeficiente(z)+b.getCoeficiente(z));return r;}

Nótese que hemos hecho un uso consistente de la directiva "const" para indicarque los objetos no son alterados. Por ejemplo, el argumento del método suma() esuna referencia "const" por lo que durante la implementación de dicho método, aquélsólo puede invocar a sus propios métodos calificados como "const", tales comogetGrado() y getCoeficiente().

Otro detalle resaltante corresponde a la asignación de los coeficientes del polino-mio: si se especifica un grado mayor al que posee actualmente el polinomio, loscoeficientes intermedios son rellenados con ceros (con el método push_back()del vector.)

Finalmente presentamos un programa de prueba:

// prueba.cc#include "polinomio.h"#include <iostream>

using namespace std;

int main(){Polinomio p,q,r;p.setCoeficiente(0,1.0);p.setCoeficiente(1,2.0);p.setCoeficiente(2,1.0);

q.setCoeficiente(0,5.0);q.setCoeficiente(1,6.0);q.setCoeficiente(2,7.0);q.setCoeficiente(3,8.0);

r=p.suma(q);

cout << "Polinomio p: "; p.display(); cout << endl;cout << "Polinomio q: "; q.display(); cout << endl;cout << "Polinomio r=p+q: "; r.display(); cout << endl;return 0;}

Page 5: de Operadores en C++ Tutorial: Sobrecarga Tabla de contenidosEn esta versión no haremos uso de la sobrecarga de operadores, limitándonos a la creación de los polinomios y a la operación

Tutorial: Sobrecarga de Operadores en C++

5

Para algunos puede ser interesante el "Makefile" utilizado para compilar los ar-chivos (se puede mejorar):

all: prueba

prueba: prueba.cc polinomio.cc polinomio.h g++ -Wall -o prueba prueba.cc polinomio.cc

El resultado del programa es:

Polinomio p: 1 2 1 Polinomio q: 8 7 6 5 Polinomio r=p+q: 8 8 8 6

Ejercicio

Modifique el programa a fin de mostrar los polinomios de un modo más familiar. Porejemplo:

1 2 1 -> x^2 + 2x + 1

Introducción de Operadores

La siguiente versión de nuestro archivo cabecera presenta dos constructores queharán más sencilla la creación de nuevos polinomios. Asimismo, hemos reempla-zado el método "suma" por el método llamado "operator+", lo que significa enrealidad sobrecarga del operador "+".

Finalmente, hemos creado una función global llamada "operator<<" que recibeuna referencia "ostream" y un polinomio. Esto sobrecarga el operador "<<" en elcontexto de los "flujos de salida de datos" (tal como "cout"); la introducción de estostipos obliga a incluir el archivo <iostream>:

// polinomio.h#include <vector>#include <iostream>

class Polinomio {public:Polinomio();Polinomio(int grado, ...);int getGrado() const;double getCoeficiente(int grado) const;void setCoeficiente(int grado, double valor);Polinomio operator+(const Polinomio &pol) const;protected:std::vector<double> coeficiente;};

std::ostream & operator<<(std::ostream &, const Polinomio &pol);

Para hacerlo más interesante, hemos definido un constructor de tipo "variadic", loque obliga a incluir el archivo cabecera <cstdarg> en la implementación, a finde disponer de las clásicas macros "va_*". Este constructor simplemente recibe el

Page 6: de Operadores en C++ Tutorial: Sobrecarga Tabla de contenidosEn esta versión no haremos uso de la sobrecarga de operadores, limitándonos a la creación de los polinomios y a la operación

Tutorial: Sobrecarga de Operadores en C++

6

grado del polinomio y asume que el resto de argumentos corresponden a los coe-ficientes del mismo (desde el grado más alto hasta el grado cero inclusive.) Comose sabe, C++ no validará que el número de argumentos proporcionados sea el ade-cuado, por lo que el usuario deberá tener mucho cuidado en este aspecto.

Por otro lado, el método "suma()" ha permanecido inalterado, salvo por su nombrereemplazado por "operator+". La directiva "operator" realiza el milagro de per-mitir una invocación directa mediante:

p+q

Sin embargo, también es posible invocarlo explícitamente:

p.operator+(q)

A continuación presentamos la implementación de los constructores, el método ope-rador de suma y la función global del operador <<. Los métodos getCoeficien-te(), getGrado() y setCoeficiente() no se alteran. El método display()desaparece.

// polinomio.cc#include "polinomio.h"#include <iostream>#include <cstdarg>

Polinomio::Polinomio(){}

Polinomio::Polinomio(int grado, ...){va_list ap;va_start(ap, grado);for(int z=0;z<=grado;z++) setCoeficiente(grado-z,va_arg(ap,double));va_end(ap);}

Polinomio Polinomio::operator+(const Polinomio &b) const{Polinomio r=*this;for(int z=0;z<=b.getGrado();z++) r.setCoeficiente(z,getCoeficiente(z)+b.getCoeficiente(z));return r;}

std::ostream & operator<<(std::ostream &out, const Polinomio &pol){for(int z=pol.getGrado();z>=0;z--) out << pol.getCoeficiente(z) << " ";return out;}

// V2

Finalmente, el programa de prueba (usuario de nuestra clase) luce ahora muchomás sencillo:

Page 7: de Operadores en C++ Tutorial: Sobrecarga Tabla de contenidosEn esta versión no haremos uso de la sobrecarga de operadores, limitándonos a la creación de los polinomios y a la operación

Tutorial: Sobrecarga de Operadores en C++

7

// prueba.cc#include "polinomio.h"#include <iostream>

using namespace std;

int main(){Polinomio p(2, 1.0,2.0,1.0), q(3, 8.0,7.0,6.0,5.0),r;

r=p+q;// r=p.operator+(q);

cout << "Polinomio p: " << p << endl;cout << "Polinomio q: " << q << endl;cout << "Polinomio r=p+q: " << r << endl;return 0;}

El resultado es el mismo:

Polinomio p: 1 2 1 Polinomio q: 8 7 6 5 Polinomio r=p+q: 8 8 8 6

Uso eficiente de referencias

En esta versión sólo introduciremos el operador "+=", sin embargo modificaremosprácticamente toda la implementación debido a que demostraremos un uso másefectivo de las referencias; concretamente, los métodos getCoeficiente() ysetCoeficiente() serán reemplazados por un único método llamado coefi-ciente().

// polinomio.h#include <vector>#include <iostream>#include <stdexcept>

class Polinomio {public:Polinomio();Polinomio(int grado, ...);int getGrado() const;double &coeficiente(int grado) throw (std::range_error);const double &coeficiente(int grado) const throw (std::range_error);Polinomio operator+(const Polinomio &pol) const;Polinomio &operator+=(const Polinomio &pol);protected:std::vector<double> data;static const double zero;};

std::ostream & operator<<(std::ostream &, const Polinomio &pol);

En primer lugar, el vector "coeficiente" ha sido renombrado a "data" puesto que es-tamos creando un método llamado precísamente "coeficiente". Asimismo se apreciaque disponemos de dos métodos con dicho nombre, uno que retorna una "referenciaa double" y otro que retorna una "referencia a un double constante". En el segundocaso es necesario especificar el método como "const" a fin de que el compilador

Page 8: de Operadores en C++ Tutorial: Sobrecarga Tabla de contenidosEn esta versión no haremos uso de la sobrecarga de operadores, limitándonos a la creación de los polinomios y a la operación

Tutorial: Sobrecarga de Operadores en C++

8

considere ambos como dos versiones (como se sabe, el valor de retorno no bastapara conseguir este objetivo.)

La implementación se muestra a continuación:

// polinomio.cc#include "polinomio.h"#include <iostream>#include <cstdarg>#include <stdexcept>

double & Polinomio::coeficiente(int grado) throw (std::range_error){if(grado<0) throw std::range_error("Grado negativo");while(grado>getGrado()) data.push_back(0);return data[grado];}

const double & Polinomio::coeficiente(int grado) const throw (std::range_error){if(grado<0) throw std::range_error("Grado negativo");if(grado > getGrado()) return zero;return data[grado];}

Polinomio::Polinomio(){}

Polinomio::Polinomio(int grado, ...){va_list ap;va_start(ap, grado);for(int z=0;z<=grado;z++) coeficiente(grado-z)=va_arg(ap,double);va_end(ap);}

Polinomio Polinomio::operator+(const Polinomio &b) const{Polinomio r=*this;for(int z=0;z<=b.getGrado();z++) r.coeficiente(z)+=b.coeficiente(z);return r;}

Polinomio &Polinomio::operator+=(const Polinomio &b){for(int z=0;z<=b.getGrado();z++) coeficiente(z)+=b.coeficiente(z);return *this;}

std::ostream & operator<<(std::ostream &out, const Polinomio &pol){for(int z=pol.getGrado();z>=0;z--) out << pol.coeficiente(z) << " ";return out;

Page 9: de Operadores en C++ Tutorial: Sobrecarga Tabla de contenidosEn esta versión no haremos uso de la sobrecarga de operadores, limitándonos a la creación de los polinomios y a la operación

Tutorial: Sobrecarga de Operadores en C++

9

}

int Polinomio::getGrado() const{return data.size()-1;}

const double Polinomio::zero=0.0;

El cambio fundamental radica en el método coeficiente(); tal como se aprecia,ambas implementaciones retornan un elemento del vector "data"; sin embargo,dado que la función tiene por tipo de retorno una "referencia a double", es posibleutilizar este valor retornado para modificarlo:

coeficiente(n)=valor

Es equivalente (salvo por las reglas de acceso) a:

data[n]=valor

Asimismo, es posible utilizar sólo el valor devuelto (sin modificarlo):

x=coeficiente(n)

Del mismo modo que:

x=data[n]

Todo esto se consigue con la primera forma del método "coeficiente()".

Por otro lado, si se aprecia la nueva implementación del operador sobrecargado"<<", se aprecia que el parámetro pol es una referencia "const", por lo que sólo sepuede invocar a sus métodos "const"; debido a esto fue necesario escribir la segun-da implementación de "coeficiente()", utilizable con referencias "const". A finde retornar una referencia constante, hemos empleado el miembro "zero" (constdouble), el cual es genérico para todos los objetos por lo que se declara "static".

Otro aspecto interesante radica en la introducción de excepciones para el caso enque se solicite un coeficiente de grado negativo. Es claro que esto es mucho máslimpio que utilizar un "valor mágico" de retorno, o el uso de una variable global tipo"errno". La clase de excepción "range_error" se define en el archivo <stdex-cept>.

Comportamiento "éstandar" de los operadores

El operador "+=" permite "incrementar" el objeto de la izquierda en la magnitud con-tenida en el objeto de la derecha:

p+=q

En nuestra implementación del operador "+=" hemos respetado su comportamiento"estándar" presente en los tipos de dato nativos: en primer lugar, el objeto de laizquierda ha sufrido una modificación mientras que el de la derecha permanececonstante; en segundo lugar, tras concluirse operación, se retorna como resultado

Page 10: de Operadores en C++ Tutorial: Sobrecarga Tabla de contenidosEn esta versión no haremos uso de la sobrecarga de operadores, limitándonos a la creación de los polinomios y a la operación

Tutorial: Sobrecarga de Operadores en C++

10

una referencia al objeto de la izquierda, la que puede ser utilizada como parte deotra expresión. Todo esto se puede comprender fácilmente con números enteros:

int a=1,b=2,c;

c=10+(a+=b);

En el paréntesis, "a" queda incrementado en "b" unidades (este último no se altera)y el nuevo valor de "a" es agregado al "10" para obtener un nuevo resultado de 13unidades.

Nuestra implementación del operador "+=" ha seguido fielmente estas reglas, cosaque es muy conveniente puesto que cualquier programador espera este comporta-miento; sin embargo, el lenguaje no obliga a esto. Por ejemplo, el operador podríaalterar el objeto de la derecha, o retornar un nuevo tipo de objeto, etc. Nada de estoes recomendable en tanto generará desconcierto en los usuarios de la clase.

Finalmente presentamos el programa de prueba que ilustra el nuevo operador:

// prueba.cc#include "polinomio.h"#include <iostream>

using namespace std;

int main(){Polinomio p(2, 1.0,2.0,1.0), q(3, 8.0,7.0,6.0,5.0),r;r=p+q;cout << "Polinomio p: " << p << endl;cout << "Polinomio q: " << q << endl;cout << "Polinomio r=p+q: " << r << endl;q+=p;cout << "Polinomio q+=p: " << q << endl;return 0;}

El resultado es correcto:

Polinomio p: 1 2 1 Polinomio q: 8 7 6 5 Polinomio r=p+q: 8 8 8 6 Polinomio q+=p: 8 8 8 6

Sobrecarga del operador de "subscript"

Nos referimos a los corchetes usados como índices de elementos de los arrays([ y ]). En el ejemplo anterior basta con reemplazar ambas implementaciones de"coeficiente()" con "operator[]" para obtener el comportamiento deseado:

// polinomio.h#include <vector>#include <iostream>#include <stdexcept>

class Polinomio {

Page 11: de Operadores en C++ Tutorial: Sobrecarga Tabla de contenidosEn esta versión no haremos uso de la sobrecarga de operadores, limitándonos a la creación de los polinomios y a la operación

Tutorial: Sobrecarga de Operadores en C++

11

public:Polinomio();Polinomio(double v);Polinomio(int grado, ...);int getGrado() const;double &operator[](int grado) throw (std::range_error);const double &operator[](int grado) const throw (std::range_error);Polinomio operator+(const Polinomio &pol) const;Polinomio &operator+=(const Polinomio &pol);protected:std::vector<double> data;static const double zero;};

std::ostream & operator<<(std::ostream &, const Polinomio &pol);

En la implementación simplemente hacemos el reemplazo de las expresiones de laforma "r.coeficiente(n)" por "r[n]"; sin embargo, el caso de las expresionesque invocan a "coeficiente(n)" sin el objeto (es decir, las que se refieren alpropio objeto) se deben manejar con la forma "(*this)[n]" puesto que "*this"es una referencia al objeto en uso.

// polinomio.cc#include "polinomio.h"#include <iostream>#include <cstdarg>#include <stdexcept>

double & Polinomio::operator[](int grado) throw (std::range_error){if(grado<0) throw std::range_error("Grado negativo");while(grado>getGrado()) data.push_back(0);return data[grado];}

const double & Polinomio::operator[](int grado) const throw (std::range_error){if(grado<0) throw std::range_error("Grado negativo");if(grado > getGrado()) return zero;return data[grado];}

Polinomio::Polinomio(){}

Polinomio::Polinomio(double value){(*this)[0]=value;}

Polinomio::Polinomio(int grado, ...){va_list ap;va_start(ap, grado);for(int z=0;z<=grado;z++) (*this)[grado-z]=va_arg(ap,double);va_end(ap);

Page 12: de Operadores en C++ Tutorial: Sobrecarga Tabla de contenidosEn esta versión no haremos uso de la sobrecarga de operadores, limitándonos a la creación de los polinomios y a la operación

Tutorial: Sobrecarga de Operadores en C++

12

}

Polinomio Polinomio::operator+(const Polinomio &b) const{Polinomio r=*this;for(int z=0;z<=b.getGrado();z++) r[z]+=b[z];return r;}

Polinomio &Polinomio::operator+=(const Polinomio &b){for(int z=0;z<=b.getGrado();z++) (*this)[z]+=b[z];return *this;}

std::ostream & operator<<(std::ostream &out, const Polinomio &pol){for(int z=pol.getGrado();z>=0;z--) out << pol[z] << " ";return out;}

int Polinomio::getGrado() const{return data.size()-1;}

const double Polinomio::zero=0.0;

Adicionalmente hemos escrito un constructor que crea un polinomio a partir de unvalor "double"; este nuevo polinomio tiene grado cero y su único coeficiente corres-ponde al valor "double" proporcionado.

El uso de este constructor se puede ver en acción en el programa de prueba:

// prueba.cc#include "polinomio.h"#include <iostream>

using namespace std;

int main(){Polinomio p(2, 1.0,2.0,1.0), q(3, 8.0,7.0,6.0,5.0),r;Polinomio s(5.0);r=p+q;cout << "Polinomio p: " << p << endl;cout << "Polinomio q: " << q << endl;cout << "Polinomio r=p+q: " << r << endl;q+=p;cout << "Polinomio q+=p: " << q << endl;cout << "Polinomio s: " << s << endl;q+=2.0;cout << "Polinomio q+=2.0: " << q << endl;return 0;}

Cuyo resultado es:

Polinomio p: 1 2 1

Page 13: de Operadores en C++ Tutorial: Sobrecarga Tabla de contenidosEn esta versión no haremos uso de la sobrecarga de operadores, limitándonos a la creación de los polinomios y a la operación

Tutorial: Sobrecarga de Operadores en C++

13

Polinomio q: 8 7 6 5 Polinomio r=p+q: 8 8 8 6 Polinomio q+=p: 8 8 8 6 Polinomio s: 5 Polinomio q+=2.0: 8 8 8 8

En el caso del objeto "s", es claro que el constructor está siendo invocado con elvalor (double) 5.0, por lo que el resultado no es sorprendente; sin embargo, es inte-resante apreciar que hemos logrado pasar un valor (double) igual a 2.0 al operador"+=", el cual supuestamente sólo recibe objetos de tipo "Polinomio" en el lado de-recho. La razón del éxito radica en que el nuevo constructor permite crear "al vue-lo" un nuevo polinomio que puede aplicarse en el lado derecho del operador "+=";otra forma de comprenderlo consiste en pensar que el tipo de dato "double" se haconvertido en el tipo de dato "Polinomio" debido a que esto es lo único factible enel contexto del operador "+=".

De otro lado, lamentablemente este nuevo constructor crea un potencial problemapara la fortaleza de la clase: en tanto ya hay otro constructor que recibe un número(entero) como primer argumento, es relativamente sencillo confundir ambos. Porejemplo, si se especifica:

Polinomio p(10);

El compilador interpreta (correctamente) el valor 10 como un entero e invocará (ca-tastróficamente) al constructor de tipo "variadic", el cual posiblemente hará caer elprograma o en el mejor de los casos construirá un polinomio con 10 valores total-mente carentes de sentido. A fin de solicitar un polinomio de grado cero debemosescribir algo como:

Polinomio p(10.0);

Como se indicó, esto es muy fácil de ser pasado por alto por los usuarios de la clasepor lo que se desaconseja totalmente.

Conmutatividad

Gracias al constructor que se agregó en la última implementación, es factible realizaroperaciones con objetos Polinomio tales como:

p=q+2.0;

(pruébelo!) Es natural esperar que también funcione la operación con los términosinvertidos:

p=2.0+q;

Sin embargo, esto genera un error de compilación. La razón radica sencillamente enque el valor de la izquierda (el número "2.0") no posee un operador "+" que soportea la derecha un objeto "Polinomio" (lo inverso sí es cierto.)

A fin de implementar esta funcionalidad debemos escribir una función global quesobrecargue el operador "+" para objetos Polinomio a ambos lados de la expre-sión. De pasada, esto elimina la necesidad de incluir el método "operator+()" alinterior de la clase:

Page 14: de Operadores en C++ Tutorial: Sobrecarga Tabla de contenidosEn esta versión no haremos uso de la sobrecarga de operadores, limitándonos a la creación de los polinomios y a la operación

Tutorial: Sobrecarga de Operadores en C++

14

// polinomio.h#include <vector>#include <iostream>#include <stdexcept>

class Polinomio {public:Polinomio();Polinomio(double v);Polinomio(int grado, ...);int getGrado() const;double &operator[](int grado) throw (std::range_error);const double &operator[](int grado) const throw (std::range_error);Polinomio &operator+=(const Polinomio &pol);protected:std::vector<double> data;static const double zero;};

Polinomio operator+(const Polinomio &a,const Polinomio &b);std::ostream & operator<<(std::ostream &, const Polinomio &pol);

En la implementación eliminamos el antiguo método Polinomio::operator+()y agregamos la función global operator+(); el resto permanece igual:

// polinomio.cc#include "polinomio.h"#include <iostream>#include <cstdarg>#include <stdexcept>

Polinomio operator+(const Polinomio &a,const Polinomio &b){Polinomio r=a;r+=b;return r;}

// V5

Finalmente el programa de prueba:

// prueba.cc#include "polinomio.h"#include <iostream>

using namespace std;

int main(){Polinomio p(2, 1.0,2.0,1.0), q(3, 8.0,7.0,6.0,5.0),r;Polinomio s(5.0);r=p+q;cout << "Polinomio p: " << p << endl;cout << "Polinomio q: " << q << endl;cout << "Polinomio r=p+q: " << r << endl;q+=p;cout << "Polinomio q+=p: " << q << endl;cout << "Polinomio s: " << s << endl;q=2.0+p;cout << "Polinomio q=2.0+p: " << q << endl;return 0;

Page 15: de Operadores en C++ Tutorial: Sobrecarga Tabla de contenidosEn esta versión no haremos uso de la sobrecarga de operadores, limitándonos a la creación de los polinomios y a la operación

Tutorial: Sobrecarga de Operadores en C++

15

}

Y su resultado:

Polinomio p: 1 2 1 Polinomio q: 8 7 6 5 Polinomio r=p+q: 8 8 8 6 Polinomio q+=p: 8 8 8 6 Polinomio s: 5 Polinomio q=2.0+p: 1 2 3

Para terminar, nótese que el operador "+=" no soporta conmutatividad por lo quedebe mantenerse al interior de la clase.

Optimizando la implementación: funciones friend

La función operator<<() imprime los coeficientes del polinomio obteniéndolosmediante el operador de "subscript" ([]) que a su vez hace ciertas verificacionesantes de devolver la referencia correspondiente. En esta sección deseamos haceruna "optimización ilustrativa" para lo cual trataremos de acceder directamente alvector "data", evitando las referidas "verificaciones". Como se sabe, data es unmiembro "protegido" de la clase, por lo que una función externa como operator<<no puede accederlo directamente.

Para estos casos existen las declaraciones de funciones "friend", las cuales se de-finen al interior de la clase pero sin llegar a convertirlas en miembros de la misma(en particular, no se asocian a los objetos.) Tanto operator<< como operator+son candidatos para esta clase de optimización por lo que reescribimos sus decla-raciones:

// polinomio.h#include <vector>#include <iostream>#include <stdexcept>

class Polinomio {public:Polinomio();Polinomio(double v);Polinomio(int grado, ...);int getGrado() const;double &operator[](int grado) throw (std::range_error);const double &operator[](int grado) const throw (std::range_error);Polinomio &operator+=(const Polinomio &pol);// Funciones friend:friend Polinomio operator+(const Polinomio &a,const Polinomio &b);friend std::ostream & operator<<(std::ostream &, const Polinomio &pol);protected:std::vector<double> data;static const double zero;};

Y eso es todo: la implementación permanece inalterada, salvo por nuestra optimi-zación:

// polinomio.cc

Page 16: de Operadores en C++ Tutorial: Sobrecarga Tabla de contenidosEn esta versión no haremos uso de la sobrecarga de operadores, limitándonos a la creación de los polinomios y a la operación

Tutorial: Sobrecarga de Operadores en C++

16

#include "polinomio.h"#include <iostream>#include <cstdarg>#include <stdexcept>

std::ostream & operator<<(std::ostream &out, const Polinomio &pol){for(int z=pol.data.size()-1;z>=0;z--) out << pol.data[z] << " ";return out;}

// V6

Conversión de tipos

La directiva "operator" permite especificar una "conversión" automática de tipopara la clase. El ejemplo clásico consiste en obtener un "string" a partir de un objetomediante una asignación tal como:

Polinomio p;...string x=p;

A fin de que esto funcione, es necesario agregar un método a la clase Polinomioque describa cómo se obtendrá el string deseado (de lo contrario el código nocompilará.)

La definición de la clase require una línea adicional:

// polinomio.h#include <vector>#include <iostream>#include <stdexcept>#include <string>

class Polinomio {public:Polinomio();Polinomio(double v);Polinomio(int grado, ...);int getGrado() const;double &operator[](int grado) throw (std::range_error);const double &operator[](int grado) const throw (std::range_error);Polinomio &operator+=(const Polinomio &pol);// Conversion de tipo:operator std::string() const;friend Polinomio operator+(const Polinomio &a,const Polinomio &b);friend std::ostream & operator<<(std::ostream &, const Polinomio &pol);protected:std::vector<double> data;static const double zero;};

Y la implementación sólo requiere agregar el método que retorna el string:

// polinomio.cc

Page 17: de Operadores en C++ Tutorial: Sobrecarga Tabla de contenidosEn esta versión no haremos uso de la sobrecarga de operadores, limitándonos a la creación de los polinomios y a la operación

Tutorial: Sobrecarga de Operadores en C++

17

#include "polinomio.h"#include <iostream>#include <cstdarg>#include <stdexcept>#include <string>#include <sstream>

Polinomio::operator std::string() const{std::ostringstream ostr;for(int z=getGrado();z>=0;z--) ostr << (*this)[z] << " ";return ostr.str();}

// V7

La sintaxis es extraña pues parece que la función no retorna ningún tipo; sin em-bargo, el tipo viene dado después de la directiva operator.

El programa de prueba:

// prueba.cc#include "polinomio.h"#include <iostream>#include <string>

using namespace std;

int main(){Polinomio p(2, 1.0,2.0,1.0);string str_p=p;cout << "Polinomio p como objeto: " << p << endl;cout << "Polinomio p como string: " << str_p << endl;return 0;}

Y el resultado:

Polinomio p como objeto: 1 2 1 Polinomio p como string: 1 2 1

Polinomios Especializados

La clase Polinomio definida hasta el momento sirve de base para efectuar diver-sas operaciones típicas. En esta sección asumiremos que se van a crear nuevospolinomios con más funcionalidad, para lo cual crearemos una subclase llamadaPoliPower, la cual posee un nuevo método llamado traza() que proporciona lasuma de los coeficientes:

// polipower.h#include "polinomio.h"#include <stdexcept>

class PoliPower : public Polinomio {public:PoliPower();

Page 18: de Operadores en C++ Tutorial: Sobrecarga Tabla de contenidosEn esta versión no haremos uso de la sobrecarga de operadores, limitándonos a la creación de los polinomios y a la operación

Tutorial: Sobrecarga de Operadores en C++

18

PoliPower(double v);double traza() const throw (std::runtime_error);};

Aprovecharemos este ejemplo para ilustrar el uso de dynamic_cast con referen-cias a objetos. Esta funcionalidad requiere que las clases bases sean "polimórficas",lo cual se consigue cuando cualquiera de sus métodos es declarado "virtual".Para nuestro caso, la forma más simple de conseguirlo consiste en declarar al des-tructor como método virtual (el destructor no requiere ningún contenido para nuestroejemplo.) A continuación se muestra la primera parte del nuevo polinomio.h conel destructor virtual:

// polinomio.h#include <vector>#include <iostream>#include <stdexcept>#include <string>

class Polinomio {public:Polinomio();Polinomio(double v);Polinomio(int grado, ...);virtual ~Polinomio();// V8

La implementacion de la clase PoliPower es directa. El único detalle interesanteradica en que no disponemos ya del constructor de argumentos variables (los cons-tructores no se heredan) y el constructor con argumento double invoca al cons-tructor de Polinomio con su mismo argumento.

// polipower.cc#include "polipower.h"#include <stdexcept>

double PoliPower::traza() const throw (std::runtime_error){if(data.size()==0) throw std::runtime_error("Polinomio mal definido");double r=0;for(unsigned int z=0;z<data.size();z++) r+=data[z];return r;}

PoliPower::PoliPower(){}

PoliPower::PoliPower(double x):Polinomio(x){}

La implementación de la clase Polinomio sólo requiere agregar el destructor trivial:

// polinomio.cc#include "polinomio.h"#include <iostream>#include <cstdarg>

Page 19: de Operadores en C++ Tutorial: Sobrecarga Tabla de contenidosEn esta versión no haremos uso de la sobrecarga de operadores, limitándonos a la creación de los polinomios y a la operación

Tutorial: Sobrecarga de Operadores en C++

19

#include <stdexcept>#include <string>#include <sstream>

Polinomio::~Polinomio(){}// V8

En cuanto a la prueba, mezclaremos polinomios con polipowers:

// prueba.cc#include "polipower.h"#include <iostream>#include <string>

using namespace std;

int main(){PoliPower p;p[0]=3.0; p[1]=4.0; p[2]=5.0;string str_p=p;Polinomio q(3, 8.0,7.0,6.0,5.0),r;PoliPower s(5.0);r=p+q;cout << "PoliPower p como objeto: " << p << endl;cout << "PoliPower p como string: " << str_p << endl;cout << "Polinomio q: " << q << endl;cout << "Polinomio r=p+q: " << r << endl;q+=p;cout << "Polinomio q+=p: " << q << endl;cout << "PoliPower s: " << s << endl;q=2.0+p;cout << "Polinomio q=2.0+p: " << q << endl;cout << "Traza de p: " << p.traza() << endl;try { s=dynamic_cast<PoliPower &>(p+=q);} catch(const std::bad_cast &e) { cout << "Excepcion en primer dynamic cast" << endl;}try { s=dynamic_cast<PoliPower &>(q+=p);} catch(const std::bad_cast &e) { cout << "Excepcion en segundo dynamic cast" << endl;}return 0;}

La primera parte realiza las operaciones usuales, las cuales funcionan sin mayorproblema debido a que únicamente estamos pasando referencias a PoliPowerdonde se esperan referencias a Polinomio, lo cual es completamente seguro (loinverso no es cierto.)

Un buen ejercicio consiste en predecir si se lanzarán las excepciones de las últi-mas líneas. Como se sabe, cuando el "dynamic_cast" no puede convertir (down-cast) las referencias, lanza una excepción de tipo std::bad_cast, mientras queen el caso de punteros, simplemente retorna NULL. Para nuestro ejemplo, el primer"dynamic_cast" recibe el resultado de "p+=q", que dada la definición del operadorcorresponde a una referencia de tipo "Polinomio &" al objeto de la izquierda (trassu modificación.) Debido a que "Polinomio" es polimórfico y está en la jerarquía de

Page 20: de Operadores en C++ Tutorial: Sobrecarga Tabla de contenidosEn esta versión no haremos uso de la sobrecarga de operadores, limitándonos a la creación de los polinomios y a la operación

Tutorial: Sobrecarga de Operadores en C++

20

"PoliPower", la sentencia compila; asimismo, debido a que el objeto de la izquierda(p) es en realidad un "PoliPower", su referencia "Polinomio &" puede ser convertidaen una referencia "PoliPower &" en tiempo de ejecución.

En el segundo caso se verifican todas las condiciones salvo la última: el objeto de laizquierda (q) no es un "PoliPower" por lo que su referencia retornada por el operadorno puede transformarse en una "PoliPower &", lo que genera una excepción.

A continuación la salida del programa:

PoliPower p como objeto: 5 4 3 PoliPower p como string: 5 4 3 Polinomio q: 8 7 6 5 Polinomio r=p+q: 8 12 10 8 Polinomio q+=p: 8 12 10 8 PoliPower s: 5 Polinomio q=2.0+p: 5 4 5 Traza de p: 12Excepcion en segundo dynamic cast

Para los curiosos, el Makefile (¡que se puede mejorar mucho!):

all: prueba

prueba: prueba.cc polinomio.cc polinomio.h polipower.h polipower.cc g++ -Wall -o prueba prueba.cc polinomio.cc polipower.cc

Un Template de Polinomios

Dejando de lado el tema de la herencia, retrocederemos a la clase Polinomio y laconvertiremos en un "template" capaz de almacenar coeficientes de cualquier tipo(no sólo "double".)

A continuación la definición de la plantilla de la clase:

// polinomio.h#include <vector>#include <iostream>#include <stdexcept>#include <string>#include <sstream>

template <typename T>class Polinomio {public:Polinomio();Polinomio(const T& v);int getGrado() const;T& operator[](int grado) throw (std::range_error);const T& operator[](int grado) const throw (std::range_error);Polinomio<T>& operator+=(const Polinomio<T>& pol);operator std::string() const;protected:std::vector<T> data;static const T zero;};

Page 21: de Operadores en C++ Tutorial: Sobrecarga Tabla de contenidosEn esta versión no haremos uso de la sobrecarga de operadores, limitándonos a la creación de los polinomios y a la operación

Tutorial: Sobrecarga de Operadores en C++

21

#include "polinomio.cc"

Como se aprecia, la implementación de los métodos (polinomio.cc) es automá-ticamente incluida en la última línea (aunque también se acostumbra escribirla en elmismo archivo de la definición de la clase.) A continuación el contenido del archivode implementación:

// polinomio.cc

template <typename T>Polinomio<T>::operator std::string() const{std::ostringstream ostr;for(int z=getGrado();z>=0;z--) ostr << (*this)[z] << " ";return ostr.str();}

template <typename T>std::ostream & operator<< (std::ostream &out, const Polinomio<T>& pol){for(int z=pol.getGrado();z>=0;z--) out << pol[z] << " ";return out;}

template <typename T>Polinomio<T> operator+ (const Polinomio<T>& a,const Polinomio<T>& b){Polinomio<T> r=a;r+=b;return r;}

template <typename T>T& Polinomio<T>::operator[](int grado) throw (std::range_error){if(grado<0) throw std::range_error("Grado negativo");while(grado>getGrado()) data.push_back(T());return data[grado];}

template <typename T>const T& Polinomio<T>::operator[](int grado) const throw (std::range_error){if(grado<0) throw std::range_error("Grado negativo");if(grado > getGrado()) return zero;return data[grado];}

template <typename T>Polinomio<T>::Polinomio(){}

template <typename T>Polinomio<T>::Polinomio(const T& value){

Page 22: de Operadores en C++ Tutorial: Sobrecarga Tabla de contenidosEn esta versión no haremos uso de la sobrecarga de operadores, limitándonos a la creación de los polinomios y a la operación

Tutorial: Sobrecarga de Operadores en C++

22

(*this)[0]=value;}

template <typename T>Polinomio<T>& Polinomio<T>::operator+=(const Polinomio<T>& b){for(int z=0;z<=b.getGrado();z++) (*this)[z]+=b[z];return *this;}

template <typename T>int Polinomio<T>::getGrado() const{return data.size()-1;}

template <typename T>const T Polinomio<T>::zero=T();

Es interesante anotar que las antiguas funciones "friend" han sido extraídas de laclase (ahora son simples globales) a fin de simplificar la exposición (en relación alos "templates" tienen una sintaxis bastante molesta.)

El siguiente programa de prueba hace uso de la plantilla de polinomios definiéndoloscon coeficientes "double":

// prueba.cc#include "polinomio.h"#include <iostream>#include <string>

using namespace std;

int main(){Polinomio<double> p,q;

p[0]=5.0; p[1]=6.0; p[2]=10.0;q[0]=1.0; q[1]=2.0; q[2]=3.0;string str_p=p;cout << "Polinomio p como objeto: " << p << endl;cout << "Polinomio p como string: " << str_p << endl;cout << "Polinomio q: " << q << endl;p+=q;cout << "Polinomio p+=q: " << p << endl;p=p+q;cout << "Polinomio p=p+q: " << p << endl;p[10]=1.0;cout << "Nuevo Polinomio p: " << p << endl;

return 0;}

Su resultado no es sorprendente:

Polinomio p como objeto: 10 6 5 Polinomio p como string: 10 6 5 Polinomio q: 3 2 1 Polinomio p+=q: 13 8 6 Polinomio p=p+q: 16 10 7 Nuevo Polinomio p: 1 0 0 0 0 0 0 0 16 10 7

Page 23: de Operadores en C++ Tutorial: Sobrecarga Tabla de contenidosEn esta versión no haremos uso de la sobrecarga de operadores, limitándonos a la creación de los polinomios y a la operación

Tutorial: Sobrecarga de Operadores en C++

23

Ahora aplicaremos la magia de los templates. Reemplazamos el tipo "double" porun número complejo basado en pares de "doubles" (tipo complex<double>); elresto del programa permanece incólume:

// prueba2.cc#include "polinomio.h"#include <iostream>#include <string>#include <complex>

using namespace std;

int main(){Polinomio< complex<double> > p,q;// V9

La salida esta vez sí es sorprendente:

Polinomio p como objeto: (10,0) (6,0) (5,0) Polinomio p como string: (10,0) (6,0) (5,0) Polinomio q: (3,0) (2,0) (1,0) Polinomio p+=q: (13,0) (8,0) (6,0) Polinomio p=p+q: (16,0) (10,0) (7,0) Nuevo Polinomio p: (1,0) (0,0) (0,0) (0,0) (0,0) (0,0) (0,0) (0,0) (16,0) (10,0) (7,0)

Para terminar el Makefile. Obsérvese que ya no se compila por sepa-rado a "polinomios.cc" puesto que es automáticamente incluido desde"polinomios.h", y es que en realidad no contiene nada compilable hasta el mo-mento en que la plantilla es instanciada.

all: prueba prueba2

prueba: prueba.cc polinomio.cc polinomio.h g++ -Wall -o prueba prueba.cc

prueba2: prueba2.cc polinomio.cc polinomio.h g++ -Wall -o prueba2 prueba2.cc

A. Bibliografía Recomendada

• Solter, N. & Kleper, S.: Professional C++. Willey Publishing, Inc. USA. 2005.

• Meyers, S. : Effective C++. 2nd edition. Addison-Wesley. USA. 1998

• Stroustrup, B.: The C++ Programming Language. 3rd edition. Addisson-Wesley.USA. 1997.