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 */) { // ...
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.