martes, 8 de septiembre de 2009

ADL y clases parciales

Sobrecarga de operadores

La sobrecarga de operadores es una técnica útil y que incluso los que la despreciaban están, poco a poco, admitiendo que es necesaria. Esta técnica consiste en asociar operadores como +, *, <<>

a=b+c

puede ser una suma de enteros, de flotantes u otra cosa. Obviamente, si es una operación de suma de matrices o una concateniación de cadenas es muy útil, pero si es otra cosa poco intuitiva el programa pierde legibilidad. Estas son las ventajas e inconvenientes. Como generalmente se suele hacer buen uso de la sobrecarga de operadores, ha terminado siendo una técnica deseada.

En C++ las funciones asociadas a un operador "*" se llaman "operator *", así que la suma de arriba llamaría a una función con nombre "operator +". Según el tipo de sus argumentos, ya buscaría una sobrecarga adecuada de esta función.

Espacios de nombre

Por otra parte tenemos los espacios de nombres. Un espacio de nombre no es más que un nombre con nombres "dentro". Puedo tener el nombre "x" suelto (en el espacio de nombres global) o el nombre "x" dentro del nombre "y" (en el espacio de nombres "y"). En C++ al primero lo llamaríamos "x" y al segundo "y::x". Los espacios de nombres son importantes porque organizan y evitan las colisiones entre nombres. Un programador podría usar su "x" dentro de su espacio de nombres "fulanito" y otro programador su "x" dentro de su espacio de nombres "menganito". Estas "x" no se confundirían.

El C++ mete sus definiciones de la biblioteca estándar en el espacio de nombres "std". Muchos otros lenguajes hacen cosas similares con otros espacios de nombres como el "java" del Java (aunque en Java los espacios de nombre están relacionados con los módulos por lo que se llaman paquetes). Particularmente, los stream de entrada/salida de C++ están dentro de "std" por lo que tengo que escribir

std::cout << "Hola";

El problema es que este código no compilaría porque, al haber sobrecarga de operadores, el "<<" es una función "operator <<" pero ¡¡que está dentro del espacio de nombres "std"!! Es decir, que existe una incompatibilidad entre los espacios de nombre y los operadores. Lo de arriba debería escribirse algo como

std::operator<< (std::cout, "Hola")

lo cual es inaceptable. ¡Queremos operadores, no funciones!

ADL

La forma que tuvieron los señores de la comisión de estandarización del C++ (y en particular el señor Koenig) de resolver este problema fue la siguiente. Cuando veamos una función, buscamos su nombre no sólo en el espacio de nombres global sino que también en el espacio de nombres de sus argumentos.

De esta forma cuando escribo

std::cout << "Hola";

descubro que "operator <<" está sin definir en el espacio de nombres global. Entonces, veo que uno de sus argumentos (el de la izquierda) proviene del espacio de nombres "std". Así pues, voy a buscar dentro de "std" el "operator <<" y ¡allí está!

A este procedimiento se le llama Argument Dependent Lookup (ADL).

Más allá del ADL

Realmente, el ADL lo tienen todos los lenguajes de programación orientados a objeto de forma encubierta. Ocurre cuando usamos un miembro de una clase. Al escribir

class MiEntero { public: int suma(int b){......} };

MiEntero mi;

mi.suma(3)

el nombre "suma" es un nombre que está dentro del nombre "MiEntero". Aquí las clases se comportan como espacios de nombre y no tenemos ese problema de saber si "suma" es el "suma" que busco o no. ¡Claro que es ese! El que está en el objeto "mi" de la clase "MiEntero". No hay confusión.

Esta técnica se usó también en C++ de manera que se permite definir funciones miembro "operator *" dentro de las clases. El problema con el que nos encontramos es que sólo podemos utilizar el espacio de nombres del argumento de la izquierda.

mi.suma(3) //OK

3.suma(mi) //Error ("suma" que tome un "MiEntero" por la derecha no está definido en "int")

Parece una tontería, pero si usamos sobrecarga de operadores tenemos un problema de compilación de cuidado.

class MiEntero { public: int operator+(int b){......} };

MiEntero mi;

mi + 3 //OK

3 + mi //Error ("operator +" que tome un "MiEntero" por la derecha no está definido en "int")

Clases parciales

Es debido a la limitación de arriba que los señores del C++ eligieron extender ADL para todos los argumentos. Ni que decir tiene que en este caso se buscan muchos nombres y resulta difícil saber qué función se va a usar realmente.

Existe otra forma de solventar el problema de arriba y consiste en añadir métodos a clases (o tipos) ya definidas como "int". Esto es lo que se denomina tener clases parciales. Las clases parciales no son cerradas y siempre se les puede añadir algo más. Imaginemos una palabra clave "continue class" que nos permitiera hacer esto:

continue class int { public: MiEntero operator+(MiEntero x) {......}};

De forma que pudiéramos añadir un nuevo método en la clase. Esto es algo muy parecido a los "extension methods" de C# 3.0. Ahora cuando escriba

3 + mi //OK (Fue añadido dentro de "int" por la clase parcial)

Conclusiones

Los señores del C++ se equivocaron al introducir el ADL porque con él introdujeron una gran complicación tanto en los compiladores como a los programadores. Si hubieran usado clases parciales, incluso restringiendo las extensiones a sólo métodos, la búsqueda de nombres habría sido lineal y tersa. Sólo habría que buscar el nombre en un espacio de nombres (la clase).

Como descargo, hay que decir que en la época en la que se introdujo el ADL en C++ (en 1995) el uso de las clases parciales no era aún ni muy común ni muy conocido.

Finalmente, se puede simplificar aún más el sistema utilizando la técnica de Scala de no poner ni punto ni paréntesis a la hora de llamar a un método. En Scala es lo mismo escribir

a.b(c)

que escribir

a b c

Lo cual es muy útil si "b" es por ejemplo "+"

a.+(c)

a + c

Esto requiere algunos cambios drásticos en la gramática del lenguaje ya que han de permitirse identificadores simbólicos y hay que tratar los operadores prefijos con cuidado.

2 comentarios:

Selah dijo...

Hola. Bueno mi comentario es que deberian de poner ejemplos de programas de sobrecarga de operadores, para entenderle mas.

Gadelan dijo...

Hola Selah. Puedes ver un ejemplo muy muy simple de sobrecarga de operadores en C++ en la página de la Wikipedia (enlace)

Publicar un comentario en la entrada