7.4. Argumentos por defecto

En Stash3.h, examine los dos constructores para Stash. No parecen muy diferentes, ¿verdad?. De hecho el primer constructor parece ser un caso especial del segundo pero con size inicializado a cero. Es un poco una pérdida de tiempo y esfuerzo crear y mantener dos versiones diferentes de una función similar.

C++ proporciona un remedio mediante los argumentos por defecto. Un argumento por defecto es una valor que se da en la declaración para que el compilador lo inserte automáticamente en el caso de que no se proporcione en la llamada a la función. En el ejemplo de Stash, se puede reemplazar las dos funciones:

Stash(int size); // Zero quantity
Stash(int size, int initQuantity);

por ésta otra:

Stash(int size, int initQuantity = 0);

La definición de Stash(int) simplemente se quita; todo lo necesario está ahora en la definición de Stash(int, int).

Ahora, las definiciones de los dos objetos

Stash A(100), B(100, 0);

producirán exactamente los mismos resultados. En ambos casos se llama al mismo constructor, aunque el compilador substituye el segundo argumento de A automáticamente cuando ve que que el primer argumento es un entero y no hay un segundo argumento. El compilador ha detectado un argumento por defecto, así que sabe que todavía puede llamar a la función si substituye este segundo argumento, que es lo que usted le ha dicho que haga al no poner ese argumento.

Los argumentos por defecto, al igual que la sobrecarga de funciones, son muy convenientes. Ambas características le permiten utilizar un único nombre para una función en situaciones diferentes. La diferencia está en que el compilador substituye los argumentos por defecto cuando no se ponen. El ejemplo anterior en un buen ejemplo para utilizar argumentos por defecto en vez de la sobrecarga de funciones; de otra modo se encuentra con dos o más funciones que tienen signaturas y comportamientos similares. Si las funciones tienen comportamientos muy diferentes, normalmente no tiene sentido utilizar argumentos por defecto (de hecho, debería preguntarse si dos funciones con comportamientos muy diferentes deberían llamarse igual).

Hay dos reglas que se deben tener en cuenta cuando se utilizan argumentos por defecto. La primera es que sólo los últimos pueden ser por defecto, es decir, no puede poner un argumento por defecto seguido de otro que no lo es. La segunda es que una vez se empieza a utilizar los argumentos por defecto al realizar una llamada a una función, el resto de argumentos también serán por defecto (esto sigue a la primera regla).

Los argumentos por defecto sólo se colocan en la declaración de la función (normalmente en el fichero de cabecera). El compilador debe conocer el valor por defecto antes de utilizarlo. Hay gente que pone los valores por defecto comentados en la definición por motivos de documentación.

void fn(int x /* = 0 */) { // ...

7.4.1. Argumentos de relleno

Los argumentos de una función pueden declararse sin identificadores. Cuando esto se hace con argumentos por defecto, puede parecer gracioso. Puede encontrarse con

void f(int x, int = 0, float = 1.1);

En C++, la definición de la función tampoco necesita identificadores:

void f(int x, int, float flt) { /* ... */ }

En el cuerpo de la función, se puede hacer referencia a x y a flt, pero no al argumento de en medio puesto que no tiene nombre. A pesar de esto, las llamadas a función deben proporcionar un valor para este argumento de relleno: f(1) ó f(1, 2, 3,0). Esta sintaxis permite poner el argumento como un argumento de relleno sin utilizarlo. La idea es que podría querer cambiar la definición de la función para utilizar el argumento de relleno más tarde, sin cambiar todo el código en que ya se invoca la función. Por supuesto, puede obtener el mismo resultado utilizando un argumento con nombre, pero en ese caso está definiendo el argumento para el cuerpo de la función sin que éste lo utilice, y la mayoría de los compiladores darán un mensaje de aviso, dando por hecho que usted ha cometido un error. Si deja el argumento sin nombre intencionadamente, evitará la advertencia.

Más importante, si empieza utilizando un argumento que más tarde decide dejar de utilizar, puede quitarlo sin generar avisos ni fastidiar al código cliente que esté utilizando la versión anterior de la función.