Examine
the two constructors for
Stash( ).
They don’t seem all that different, do they? In fact, the first
constructor seems to be a special case of the second one with the initial
size
set to zero. It’s a bit of a waste of effort to create and maintain two
different versions of a similar function.
C++
provides a remedy with
default
arguments.
A default argument is a value given in the declaration that the compiler
automatically inserts if you don’t provide a value in the function call.
In the
Stash
example, we can replace the two functions:
Stash(int size); // Zero quantity
Stash(int size, int quantity);
with
the single function:
Stash(int size, int quantity = 0);
The
Stash(int)
definition is simply removed – all that is necessary is the single
Stash(int,
int)
definition.
Now,
the two object definitions
Stash A(100), B(100, 0);
will
produce exactly the same results. The identical constructor is called in both
cases, but for
A,
the second argument is automatically substituted by the compiler when it sees
the first argument is an
int
and that there is no second argument. The compiler has seen the default
argument, so it knows it can still make the function call if it substitutes
this second argument, which is what you’ve told it to do by making it a
default.
Default
arguments are a convenience, as function overloading is a convenience. Both
features allow you to use a single function name in different situations. The
difference is that with default arguments the compiler is substituting
arguments when you don’t want to put them in yourself. The preceding
example is a good place to use default arguments instead of function
overloading; otherwise you end up with two or more functions that have similar
signatures and similar behaviors. If the functions have very different
behaviors, it doesn’t usually make sense to use default arguments (for
that matter, you might want to question whether two functions with very
different behaviors should have the same name).
There
are two rules you must be aware of when using default arguments. First, only
trailing arguments may be defaulted. That is, you can’t have a default
argument followed by a nondefault argument. Second, once you start using
default arguments in a particular function call, all the subsequent arguments
in that function’s argument list must be defaulted (this follows from the
first rule).
Default
arguments are only placed in the declaration of a function (typically placed in
a header file).
The compiler must see the default value before it can use it. Sometimes people
will place the commented values of the default arguments in the function
definition, for documentation purposes
void
fn(int x /* = 0 */) { // ...
Placeholder
arguments
Arguments
in a function declaration can be declared without identifiers. When these are
used with default arguments, it can look a bit funny. You can end up with
void
f(int x, int = 0, float = 1.1);
In
C++ you don’t need identifiers in the function definition, either:
void
f(int x, int, float flt) { /* ... */ }
In
the function body,
x
and
flt
can be referenced, but not the middle argument, because it has no name.
Function calls must still provide a value for the placeholder, though:
f(1)
or
f(1,2,3.0).
This syntax allows you to put the argument in as a placeholder without using
it. The idea is that you might want to change the function definition to use
the placeholder later, without changing all the code where the function is
called. Of course, you can accomplish the same thing by using a named argument,
but if you define the argument for the function body without using it, most
compilers will give you a warning message, assuming you’ve made a logical
error. By intentionally leaving the argument name out, you suppress this
warning.
More
important, if you start out using a function argument and later decide that you
don’t need it, you can effectively remove it without generating warnings,
and yet not disturb any client code that was calling the previous version of
the function.