Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


4.4.1a Sobrecarga de funciones

§1  Sinopsis:

La sobrecarga de funciones, a la que nos hemos referido brevemente ( 4.4.1), es un mecanismo C++ que permite asignar el mismo nombre a funciones distintas. Para el compilador estas funciones no tienen nada en común a excepción del identificador, por lo que se trata en realidad de un recurso semántico del lenguaje que solo tiene sentido cuando se asigna el mismo nombre a funciones que realizan tareas similares en objetos diferentes.

Por ejemplo, suponiendo que tuviésemos objetos que fuesen diversos tipos de polígono (triángulo, cuadrado, pentágono, círculo, etc), tendría sentido denominar getArea a todas las funciones que calculasen el área de las diversas figuras, aunque naturalmente serían funciones distintas en cada caso. También tendría sentido denominar open a las funciones que abrieran un fichero o una línea de comunicación.

Al tratar de los operadores ( 4.9.18), veremos que para el compilador son en realidad funciones, cuyos nombres y sintaxis de invocación son un tanto especiales y que la sobrecarga de estas funciones permite, por ejemplo, extender los conceptos de suma (+), asignación (=) o identidad (==), a objetos distintos de los básicos (preconstruidos en el lenguaje). El hecho de que acciones distintas pero conceptualmente semejantes, puedan ser representadas por el mismo operador (función), resulta a la postre una gran ayuda conceptual. Por ejemplo: puede hablarse de una "suma" de enteros y de una "suma" de complejos que serán gobernadas por versiones distintas del operador suma (+).

Hablando en sentido figurado, si suponemos que para el humano (programador) todas las funciones sobrecargadas son una sola (un solo nombre), entonces puede afirmarse que tal función es polimórfica, en el sentido de que el "mismo" método o función tiene comportamiento distinto según los casos [1].

Nota: al tratar de la compilación, vimos que el mecanismo de "decoración" o "planchado" de nombres ("Name mangling" 1.4.2) hace que a la postre, ni siguiera los nombres de las funciones "polimórficas" sean iguales.

En realidad, el polimorfismo de funciones se presenta cuando estas son miembros de clases (métodos) y en circunstancias muy especiales; cuando son declaradas virtuales ( 4.11.8a). Es en estos casos donde los teóricos del lenguaje utilizan el término función polimórfica, dado que tales funciones son exactamente iguales en nombre, argumentos y valor devuelto, a pesar de lo cual pueden presentar comportamientos distintos [2].

§2  Resolución de sobrecarga

Cuando se realiza la invocación de una función sobrecargada, es decir que existen otras del mismo nombre en el mismo ámbito , el compilador decide cual de ellas se utilizará mediante un proceso denominado resolución de sobrecarga ("Overload resolution") aplicando ciertas reglas para verificar cual de las declaraciones se ajusta mejor al número y tipo de los argumentos utilizados. Es decir, donde existe máxima concordancia entre los argumentos actuales y formales ( 4.4.5). El proceso sigue unas reglas denominadas de congruencia estándar de argumentos; son las siguientes (en el orden precedencia señalado):

  1. Concordancia exacta en número y tipo. Esta concordancia puede incluir conversiones triviales, por ejemplo, nombre de matriz a puntero ( 4.3.2); nombre de función a puntero a función ( 4.2.4a), y tipoX a const tipoX.
  2. Concordancia después de realizar promociones de los tipos asimilables a enteros ( 2.2.1), por ejemplo: char; short; bool; enum (y sus correspondientes versiones unsigned) a int. También de tipos float a double [3].
  3. Concordancia después de realizar conversiones estándar. Por ejemplo: int a double; double a long double; clase-derivada* a Superclase* ( 4.2.1f); tipoX* a void* ( 4.2.1d).
  4. Concordancia después de realizar conversiones definidas por el usuario ( 4.9.18k).
  5. Concordancia usando la elipsis (...) en funciones con número variable de parámetros ( 4.4.1).


Si el análisis conduce a que dos funciones distintas concuerdan al mismo nivel máximo, entonces se produce un error y la sentencia es rehusada por el compilador declarando que existe una ambigüedad.

Cuando las versiones sobrecargadas tienen dos o más argumentos, se procede a obtener la mejor concordancia para cada uno utilizando las reglas anteriores. Si una función tiene mejor concordancia que las demás para un argumento y mejor o igual para los restantes, es invocada. En caso contrario la llamada se rehúsa declarando ambigüedad.

El orden en que hayan sido declaradas las versiones sobrecargadas no influye para nada en la precedencia anterior.

  Observe que en todo lo expuesto no se menciona para nada el valor devuelto. Esto significa que para que exista sobrecarga las funciones deben diferir en el tipo y/o número de argumentos. Por tanto, no es válido que solo difieran en el tipo de valor devuelto (ver §3 ).

  Temas relacionados:
  • Conversiones aritméticas ( 2.2.5).
  • Ejemplo comentado ( 4.9.18t1)
  • Conversión de argumentos en las funciones genéricas (plantillas 4.12.1a)
  • Polimorfismo en funciones genéricas (plantillas 4.12.1b)
  • Búsqueda en el espacio de los argumentos ( 4.1.11c)
  • Búsqueda de nombres ( Name-lookup).
§3  Sobrecarga y valores devueltos

Con objeto de que el mecanismo de sobrecarga sea independiente del contexto, los valores devueltos por las funciones no son tenidos en cuenta a efectos del mecanismo de sobrecarga. Esto tiene importantes consecuencias prácticas. Por ejemplo:

int func (int, char);       // L.1:
int func (float, char);     // Ok versión sobrecargada
void func (int, char);      // Error definición duplicada con L.1

§4  Sobrecarga y ámbito de nombres

Debe tener en cuenta que en C++ no existe el concepto de sobrecarga a través de ámbitos de nombres ( 4.1.11), por lo que las versiones sobrecargadas deben estar en el mismo ámbito. Por ejemplo:

#include <iostream.h>
namespace UNO {               // Subespacio exterior
   void func (int i) { cout << "func(int) " << endl; }
   namespace UNO_A {          // Subespacio anidado
      void func (float f) { cout << "func(float) " << endl; }
   }
}
void main (void) {            // =======
   int x = 1; float f = 1.1;
   UNO::func(x);
   UNO::func(f);
}

Salida:

func(int)
func(int)

Comprobamos que en ambos casos no ha existido sobrecarga de la función a través de los subespacios de nombres; en ambos casos se ha invocado la misma versión de func; la del subespacio exterior.

Los ámbitos de las clases derivadas no son una excepción de esta regla general ( 4.11.2b2).


§4.1
  Cuando se precisa que el mecanismo de la sobrecarga funcione a través de ámbitos distintos, debe utilizarse la declaración using ( 4.1.11c) o la directiva using ( 4.1.11c).

Nota: cuando los ámbitos se refieren a jerarquías de clases (las funciones a sobrecargar son métodos), solo puede utilizarse la declaración using ( 4.1.11.c1).


En el caso del ejemplo anterior, la utilización del mecanismo de sobrecarga podría ser como sigue:

#include <iostream.h>
namespace UNO {                 // Subespacio exterior
   void func (int i) { cout << "func(int) " << endl; }
   namespace UNO_A {            // Subespacio anidado
      void func (float f) { cout << "func(float) " << endl; }
   }
}
void main (void) {              // =======
   int x = 1; float f = 1.1;
   using UNO::func;
   using UNO::UNO_A::func;
   func(x);
   func(f);
}

Salida:

func(int)
func(float)


  Inicio.


[1]  Los partidarios de la POO dirán que el mismo (?) método tiene comportamiento distinto al ser aplicado sobre objetos distintos. Aunque estrictamente hablando no existen tales funciones "polimórficas" (el propio Stroustrup declara que la sobrecarga de funciones es simplemente un recurso notacional), sin embargo, hemos de admitir que se trata de un recurso conceptual muy bueno, por lo que coloquialmente utilizaremos expresiones como ...versiones sobrecargadas de "una" función...

[2]  Incluso en estos casos se trata de funciones distintas; aunque compartan el mismo nombre, el mismo valor devuelto y los mismos argumentos (la misma "firma"), a pesar de ello, el compilador dispone de recursos para distinguirlas.

[3]  Como puede verse, estas transformaciones tienden a convertir los tipos de menos a más precisión.