Every C++ program has at least one function (main
), and all but the most trivial programs
define additional functions. The C++ standard library provides numerous
functions that your program can call. This chapter discusses how to
declare, define, and call functions. A function declaration tells the compiler
about a function's name, return type, and parameters. A function
definition also provides the body of the
function.
See Chapter 3 for more information about function call expressions and Chapter 4 for information about statements, which make up function bodies. This chapter presents information that is common to all kinds of functions. For characteristics that are unique to member functions, see Chapter 6, and for information that pertains specifically to function templates, see Chapter 7.
The syntax descriptions in this chapter are informal. See Chapter 12 for a precise BNF grammar.
A function declaration tells the compiler about a function name and how to call the function. The actual body of the function can be defined separately (described later in this chapter). A function declaration has the following parts:
type
name
(parameters
)cv-qualifiers
except-spec
;
The parameters
,
cv-qualifiers
, and
except-spec
are optional. The
type
is required, except for constructors,
destructors, and type conversion operators. The
name
is the function name. (Each of these
parts is described later in this chapter.) Example 5-1 shows a variety of
function declarations.
Example 5-1. Declaring functions
// Function named "add", which returns type int, and takes two parameters, each // of type int. The names a and b are optional.int add(int a, int b);
// Function named "print", which takes a const string reference, and does not // return anything. The function is expanded inline.inline void print(const std::string& str)
{ std::cout << str; } // Function named "test", which takes two floating-point arguments and returns an // enumeration. This function does not throw any exceptions. enum fptest { less=-1, equal, greater };fptest test(double, double) throw( );
class demo { public: // Member function named "value", which returns type int, takes no arguments, // and is const, which means it can be called for a const object.int value( ) const;
// Function named "~demo", that is, a destructor, that is virtual. Constructors // and destructors do not have return types. Destructors do not take arguments.virtual ~demo( );
// Inline, overloaded, const type conversion operatoroperator bool( ) const { return value( ) != 0; }
};
The type
in a function declaration is
the function's return type. It is a series of type
specifiers (see Chapter 2) with
pointer and reference operators. You can mix any of the following
function specifiers freely with the type specifiers, but the
convention is to list function specifiers before other type
specifiers:
explicit
Applies only to constructors. An explicit constructor cannot be used in an implicit type conversion. See Chapter 6 for details.
inline
Tells the compiler to expand the function body at the point of the function call. An inline function must be defined in every file in which it is used, and the definition must be identical in every file. If the function body contains a local static object, including string literals, every expansion of the function in every file refers to a common object.
A function definition in a class definition is an inline
function definition, even without the use of the inline
specifier.
The inline
specifier is
a hint to the compiler, and the compiler is free to ignore the
hint. Most compilers impose a variety of restrictions on which
functions can be expanded inline. The restrictions vary from one
compiler to another. For example, most compilers cannot expand a
recursive function.
Inline functions are most often used for extremely simple
functions. For example, all standard containers have a member
function empty
, which returns
true
if the container is
empty. Some containers might implement the function as the
following inline
function:
inline bool empty( ) const { return size( ) != 0; }
virtual
Applies only to nonstatic member functions. A virtual function's definition is bound to the function call at runtime instead of at compile time. See Chapter 6 for details.
If a function's return type is void
, no value is returned to the caller.
The function does not need a return
statement. The form return;
is
permitted, or the return expression must have type void
.
If the return type is anything other than void
, every return
statement must have an expression,
the type of which can be implicitly converted to the function's return
type. See Chapter 4 for more
information about the return
statement.
A function's return type cannot be an array or function type, but it can be a pointer or reference to an array or function.
In a function declaration (but not a definition), you can use
the extern
storage class specifier.
Declarations and definitions can have linkage specifications, e.g.,
extern
"C
". See Chapter 2 for more information about
storage class and linkage.
A friend
specifier can be
used to declare a friend function. See Chapter 6 for more information.
Note that the return type is not considered in overload resolution. See Section 5.3 later in this chapter for details.
Function parameters are optional. If a function takes no
parameters, you can leave the parentheses empty or use the keyword
void
. If a function requires
parameters, the parameter list is comma-separated, in which each
parameter is a simple declaration of the following form:
type-specifiers
declarator
=expr
expr
is optional; if it is omitted,
the =
symbol is also omitted. The
type-specifiers
allow for the optional
register
and auto
storage class specifiers and pointer
and reference operators. See Chapter
2 for more information about declarators and specifiers.
In C, a function that takes no arguments requires the void
keyword, but in C++, void
is optional. Thus, void
appears most often in headers that
must be used in both C and C++ programs. For example:
#ifdef _ _cplusplus #define EXTERN extern "C" #else #define EXTERN extern #endif EXTERN int get_version(void);
In other situations, you can use whichever style you prefer.
You can omit the parameter name from the
declarator
. In a function declaration, the
name is important only to the human reader. In a function definition,
a nameless parameter cannot be used in the function body. For example,
suppose a graphics package defines a variety of shape classes, all
deriving from a common base class, shape
. Among the operations permitted on a
shape is scale
, which takes two
arguments: the scaling amounts in the x
and y
directions. Also, suppose that the
square
shape (unlike rectangle
) heeds only the x
scale factor. The square::scale
function might be written
as:
void square::scale(double xscale, double) { this->size *= xscale; }
A parameter can have cv-qualifiers
(const
and volatile
, as discussed in Chapter 2). The qualifiers have their
usual meaning in the function body. The qualifiers and storage class
specifier are not part of the function type and do not participate in
overload resolution.
A parameter can have a default argument (separated from the declarator by an equal sign). Only the right-most parameters can have default arguments. If any given parameter does not have a default argument, all parameters to its left cannot have default arguments. The default argument can be any expression. (If you want to use a comma operator, enclose the expression in parentheses.)
In a function call, arguments that have default values can be omitted, starting from the right. For each omitted argument, the default argument is substituted in the function call. Each default argument is implicitly converted to the parameter type, applying the same rules that govern initializers in declarations. The default argument expressions are evaluated every time the argument is needed in a function call. Names used in the default arguments are looked up at the point of declaration, not the point of use, as shown in Example 5-2.
Example 5-2. Declaring and using default arguments
#include <iostream> #include <ostream> namespace demo { int f( ) { return 20; } } int f( ) { return 10; } // The default argument for y is always the global f( ), even if a different f( ) // is visible where func( ) is called. int func(int x, int y = f( )) { return x + y; } int main( ) { using demo::f; std::cout << f( ) << '\n'; // Prints 20 std::cout << func(32) << '\n'; // Prints 42 }
Default arguments are cumulative in multiple declarations of the same function in the same scope. Later declarations can provide default arguments for additional parameters, in which case the declaration must omit the default arguments for parameters that already have default arguments, as shown in Example 5-3.
Example 5-3. Accumulating default arguments
void func(int x, int y); void func(int x, int y = 10); void func(int x = 20, int y); void other( ) { func( ); // Same as func(20, 10) }
Different scopes can have different default arguments. For example, the source file in which a function is defined might have different default arguments from those used in function declarations where the function is used. However, most of the time, different default arguments suggests programmer errors.
In a derived class, an overridden virtual function can have different default arguments than its counterpart in the base class. The default argument is chosen at compile time, based on the object's static type. Thus, the default arguments are typically those of the base class, even if the function actually called is from the derived class. To avoid confusion, it is best to avoid default arguments with virtual functions, or make sure they are the same for all overridden functions. (See Chapter 6 for more information about virtual functions.)
In a member function declaration, you cannot use a nonstatic data member as a default argument unless it is the member of a specific object. If you want to use the value of a data member as the default value for a parameter, use an overloaded function, as shown in Example 5-4. (See Section 5.3 for more on overloaded functions.)
The last parameter in a function declaration can be an
ellipsis (..
.), which
permits a variable number of arguments to be passed to the
function. The comma that separates the next-to-last parameter from the
ellipsis is optional. However, if portability with C is important, be
sure to include the comma. (See <cstdarg>
in Chapter 13 to learn how to access the
additional arguments.) You can use an ellipsis as the sole parameter
in a function, but there is no mechanism in standard C++ to access the
arguments from the function body. Such a declaration might be used for
an external function, however.
Only nonstatic member functions (but not constructors or
destructors) can have cv -qualifiers (const
and volatile
). They are optional,
and if used in a member function declaration, apply to the implicit
object parameter of the member function (this
). You can use const
, volatile
, neither, or both in any order.
Place cv-qualifiers after the closing parenthesis
of the function parameters and before the exception specification. The
qualifiers are part of the function type and participate in overload
resolution, so you can have multiple functions with the same name and
parameters, but with different qualifiers (but only if you do not also
have a static member function of the same name and parameters; see
Section 5.3 later in this
chapter for details).
A pointer-to-member function and a function typedef
can also have
cv-qualifiers. Only a top-level typedef
can have
cv-qualifiers; you cannot declare a typedef
that combines a function typedef
and a qualifier.
cv-qualifiers are most often used to
declare const
member functions. These functions can be called for a
const
object. In general, member
functions that do not change *this
should be declared const
. (See
Chapter 6 for more information on
how cv-qualifiers affect member functions.) Example 5-5 shows some simple uses
of qualifiers.
Example 5-5. Using qualifiers with member functions
class point { public: point(int x, int y) : x_(x), y_(y) {} int x( )const
{ return x_; } int y( )const
{ return y_; } double abs( )const
{ return sqrt(double(x( ))*x( ) + y( )*y( )); } void offset(const point& p) { // Cannot be const because offset( ) modifies x_ and y_ x_ += p.x( ); y_ += p.y( ); } private: int x_, y_; };
An exception specification tells the compiler which exceptions a function can throw. Exception specifications are optional in a function declaration and are rarely used. The syntax is:
throw (type-list
)
The type-list
is optional. The
exception specification follows the function header and
cv-qualifiers. If present, it is a
comma-separated list of type names. (See Chapter 2 for details about type
names.) Each type name is an exception type that the function can
throw. If the function throws an exception that is not listed in the
exception specification, the unexpected
function is called. If the function declaration does
not have an exception specification, the function can throw any
exception.
The default implementation of unexpected
calls terminate
to terminate the program. You can
set your own unexpected
handler,
which must call terminate
or throw
an exception. If your handler throws an exception that is not listed
in the function's exception specification, bad_exception
is thrown. If bad_exception
is not listed in the
function's exception specification, terminate
is called. In other words, if
there is an exception specification, only exceptions of the listed
types (or derived from one of the listed types) can be thrown from the
function, or else the program terminates. See <exception>
in Chapter 13 for details.
An overridden virtual function must have an exception specification that lists only types that are also listed in the base-class exception specifications. In particular, if the base-class function does not throw any exceptions, the derived class function must not throw any exceptions.
An exception specification most often marks functions that do
not throw exceptions at all (throw(
)
). Example 5-6
shows various uses of exception specifications.
Example 5-6. Declaring exception specifications
class base { public: virtual void f( )throw( )
; virtual void g( ); // Can throw anything virtual void h( )throw(std::string)
; }; class derived : public base { public: virtual void f( )throw( )
; // OK: same as base virtual void g( )throw(int)
; // OK: subset of base virtual void h( )throw(int)
; // Error: int not in base }; class more : public derived { public: virtual void f( ); // Error: can throw anything virtual void g( )throw( )
; // OK }; // Function does not throw any exceptions int noproblem(int x, int y)throw( )
try { dostuff(x); dostuff(y); return 1; } catch(...) { return 0; } derived* downcast(base* b)throw(std::bad_cast)
{ return dynamic_cast<derived*>(b); }