No voy a extenderme demasiado pero simplemente aclarar algunos puntos que suelen ser los que mas dudas generan. No escribiré todo en una sola entrada por supuesto, probablemente tome varias. En esta comenzare con lo básico del tema.
Otro detalle es que hace mucho que no posteo ninguna entrada relacionada a la programación, y siendo que es una de las áreas que mas me gusta creo que ya viene siendo hora.
Introducción
Cuando desarrollas una aplicación y se diseña la interfaz gráfica, siempre se tiene en mente el usuario final. No hay reglas generales que sirvan para diseñar una interfaz excelente que le guste a todos los usuarios, por esa razón uno trata de hacer la interfaz lo mas intuitiva posible, lo mas accesible que se pueda, y por sobre todas las cosas, fácil de manejar y de recordar.
Cuando desarrollas una clase o un conjunto de estas, también realizas un producto para un usuario final, con la diferencia que en este caso el usuario final es a la vez un programador.
Este simple hecho suele dar la falsa noción que, al ser el usuario final un programador, este tiene que entender todas las locuras que hayamos hecho a la hora de codificar. Es decir, "A fin de cuentas es un programador o no? si es muy bueno sabrá porque hice esto y aquello." son ideas que suelen existir en muchos programadores y que influyen negativamente en el resultado final.
Al igual que con la interfaz gráfica de una aplicación, la interfaz de una clase también debe diseñarse. Esta debe ser intuitiva, accesible, fácil de recordar y de manejar. Es aquí cuando entra la sobrecarga de operadores.
Para que se utiliza la sobrecarga de operadores?
La sobrecarga de operadores es una manera sencilla de simplificar los significados de ciertas operaciones que trabajen con tipos definidos por nosotros mismos (Lo que usualmente en C++ es una clase).
El objetivo final es reducir la curva de aprendizaje de nuestra clase al proveer una interfaz intuitiva que sea fácil de utilizar y recordar.
La idea principal radica en intentar simplificar los significados de las operaciones que realizamos con nuestras clases sobrecargando los operadores que mas se relacionen con lo que realmente se quiere hacer.
Si tuviésemos una clase que contiene un entero, y quisiésemos proveer una interfaz para sumar varios objetos sin utilizar sobrecarga de operadores, tendríamos que realizar un método que sume. Por ejemplo, algo así:
- MiClase Resultado = sum(Clase1,Clase2);
A simple vista no parece muy complicado, pero que pasa si queremos sumar 4 objetos?
- MiClase Resultado = sum(sum(Clase1,Clase2),sum(Clase1,Clase2));// No muy intuitivo que digamos.
Ahora veamos un ejemplo sobrecargando operadores:
- #include <iostream>
- class MiClase
- {
- private:
- int Num;
- public:
- MiClase(int Cnum) { Num = Cnum; }
-
- friend MiClase operator+(const MiClase &a, const MiClase &b);
- int ObtNum() { return Num; }
- };
- MiClase operator+(const MiClase &a, const MiClase &b)
- {
- return MiClase(a.Num + b.Num);
- }
- int main()
- {
- MiClase Clase1(10);
- MiClase Clase2(20);
//Intuitivo, c=a+b; - MiClase Resultado = Clase1 + Clase2 + Clase1 + Clase2;
- std::cout << "El resultado es: " << Resultado .ObtNum() << std::endl;
- return 0;
- }
Que tiene que ver esto con la seguridad informática?
Directamente nada, indirectamente mucho. La mayoría de las vulnerabilidades en las aplicaciones se dan por errores de diseño u por descuidos. Al proveer una interfaz intuitiva para tu clase, logras reducir la tasa de errores de los programadores que la utilicen, facilitando el desarrollo de aplicaciones mas seguras y estables.Cuando utilizar la sobrecarga de operadores?
Es normal que cuando uno intente resolver un problema, analice varias soluciones posibles, y luego bajo criterio propio elija la mas adecuada para el caso determinado. Realmente es imposible tener una regla que aplique para todos los casos, y lo que hoy es mejor para determinada aplicación, puede ser lo peor para otra, por lo tanto nunca deben tomarse reglas absolutas y siempre hay que analizar el caso en particular. Sin embargo, se pueden seguir una pauta que es clave para decidir si se debe o no sobrecargar operadores. Muchas personas usualmente me preguntan porque se utiliza la sobrecarga de operadores siendo que los beneficios que otorga no compensan el posible desorden del código. En reglas generales respondo siempre lo mismo: La sobrecarga de operadores no se utiliza para hacernos la vida mas fácil, si no para hacerle la vida mas fácil a los otros, a los usuarios de tus clases. Hacerle la vida mas fácil a los usuarios finales de tu clase es la regla principal que se debe seguir. Si se esta seguro de poder lograrlo, perfecto, pero si no se esta seguro lo mejor es volver a analizar el caso en particular ya que tal vez sobrecargar operadores no sea necesario o incluso puede volverse perjudicial.Comenzando con la sobrecarga de operadores
Antes que nada es esencial saber lo que es un operador. Un operador es un token que le indica al compilador que se van a realizar determinadas operaciones sobre objetos u variables –los operandos-. Es decir, si tenemos: a= b+c; donde a, b y c son enteros, podemos decir que tenemos 3 operandos (a, b y c) y 2 operadores (= y +). El comportamiento de la expresión y su significado están definidos por el lenguaje. Ahora bien, C++ permite redefinir el comportamiento de la mayoría de los operadores para que realicen una tarea aparentemente similar a la que originalmente realizan pero con un comportamiento especifico acorde a los tipos de datos que hayamos creado, por ende podemos tener algo como: a=b+c; donde a,b y c son objetos del tipo MiClase. Particularmente los operadores que se pueden sobrecargar son:Los que no pueden sobrecargarse son:+ - * / % ^ &
| ~ ! = < > +=
-= *= /= %= ^= &= |=
<< >> >>= <<= == != <=
>= && || ++ -- ->* ,
-> [] () new new[] delete delete[]
Cabe destacar que al sobrecargar un operador se puede modificar su comportamiento pero no cambiar el numero de operandos, ni la asociatividad ni la precedencia del operador respectivo.
- Selector directo de componente .
- Operador de indirección de puntero-a-miembro .*
- Operador de acceso a ámbito ::
- Condicional ternario ?:
- Directivas de preprocesado # y # #
- sizeof , typeid (si, son operadores también)
Sintaxis de la sobrecarga de operadores
El prototipo de una sobrecarga de operador puede definirse como:- <tipo de retorno> operator + (parametros) {logica} ;
- MiClase operator+(const MiClase &a, const MiClase &b);
- MiClase operator+(const MiClase &a, const MiClase &b)
- {
- return MiClase(a.Num + b.Num);
- }
Miembros de la clase o función friend
Una de las dudas mas recurrentes es si un método debe ser miembro de la clase o una función friend. Pero primero que nada, que significa el especificador friend? La razón por la cual existen métodos públicos, protegidos y privados radica en permitir al programador encapsular la implementación de su clase de la interfaz de esta. En la mayoría de las situaciones esto es esencial, pero hay casos específicos en el cual dicho esquema se torna un tanto rígido para lo que en realidad queremos hacer. Ahí es cuando entra el especificador friend, el cual permite que una función/clase tenga acceso total a otra clase de la cual no es miembro, saltando en cierta forma, el mecanismo de métodos públicos protegidos y privados. La ventaja principal de las funciones friend es que la sintaxis suele ser mas legible dado que la llamada a un miembro es a.func() mientras que una función friend es simplemente func(). Dado esto el programador debe decidir cual de las dos sintaxis es mas legible para el caso en particular y optar por ella. La sobrecarga de un operador en la mayoría de las oportunidades requiere acceso a datos privados de la clase (siempre que no haya un miembro wrapper mediante) y una sintaxis agradable a la vista. Esas son las razones principales por la cual generalmente las sobrecarga de los operadores van acompañadas del especificador de acceso friend. No obstante los permisos otorgados a una clase/función no son hereditarios, transitivos ni recíprocos, es decir, casi igual que en la vida real:- Si tenemos una clase A que es amiga de B, las derivadas de la clase A no necesariamente tienen que tener acceso a la clase B. Es decir, la amistad no es hereditaria
.
- Si tenemos una clase A que es amiga de B, y B es amiga de C, A no tiene porque ser amiga de C. Es decir, la amistad no es transitiva.
- Si tenemos una clase A que es amiga de B, B no tiene porque ser amiga de A. Es decir, la amistad no es reciproca.
- class Base {
- public:
- friend ostream& operator << (ostream& o, const Base& b);
- protected:
- virtual void print(ostream& o) const
- { ... }
- };
- inline std::ostream& operator<< (std::ostream& o, const Base& b)
- {
- b.print(o); // Delega el trabajo a la función polimorfica
- return o;
- }
- class Derived : public Base {
- protected:
- virtual void print(ostream& o) const
- { ... }
- };
buen post! me ha quedado muy claro. me gustaira ver el tema de templates tb
ResponderSuprimirsalu2