Every C++ program has at least one function (named 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 4 for more information about function call expressions, Chapter 5 for information about statements, which make up function bodies, Chapter 7 for information about member functions, and Chapter 8 for information on function templates.
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, as described later in this chapter. A function declaration has the following parts
typename
(
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 in the following sections. Example 6-1 shows a variety of function declarations.
Example 6-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 return 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", returning type int, // taking 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 operator. operator bool() const { return value() != 0; } };
The type is the function's return type. It is a list of type specifiers (see Chapter 3) and can include any of the following function specifiers. Function specifiers can be mixed freely with other type specifiers, but the convention is to list function specifiers before other type specifiers.
explicit
inline
inline
specifier.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, some compilers refuse to expand any function that contains a loop statement. Most compilers cannot expand a recursive 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
If the 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 whose type can be implicitly converted to the function's return type.
The return type cannot be an array or function type, but 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 3 for more information.
A friend
specifier can be used to declare a friend function. See Chapter 7 for more information.
Note that the return type is not considered in overload resolution. See Overloading, later in this chapter, for details.
The function parameters are optional. If the function takes no parameters, you can leave the parentheses empty or use the keyword void
. If the function requires parameters, the parameter list is comma-separated, where each parameter is a simple declaration of the following form:
type-specifiers declarator =
expr
where the declarator is optional. If present, it can optionally have pointer and reference operators and array dimensions. The expr part is also optional; if omitted, the =
symbol is also omitted. The type-specifiers permit the optional register
and auto
storage class specifiers. See Chapter 3 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 tells the reader that the parameter is not 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. Suppose further that the square
shape (unlike rectangle
) heeds only the x scale factor. The square::scale
function might be written as follows:
void square::scale(double xscale, double) { this->size *= xscale; }
A parameter can have cv-qualifiers (const
and volatile
). The qualifiers have their usual meaning (Chapter 3) 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 parameter does not have a default argument, all parameters to the left cannot have default arguments. The default parameter can be any expression. (If you want to use a comma operator, enclose the expression in parentheses.)
In a function call, arguments can be omitted, starting on the right. For each omitted parameter, the default argument is substituted in the function call. Each default argument is implicitly converted to the parameter type, using 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 6-2.
Example 6-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 6-3.
Example 6-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 where a function is defined might have different default arguments than those used in function declarations where the function is used. Most of the time, though, different default arguments are indicative of programmer errors.
In a member function declaration, you cannot use a nonstatic data member as a default argument, unless it refers to a specific instance. 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 6-4.
Example 6-4: Default arguments in member functions.
class example { public: void func(int x, int y = data_); // error // Achieve the desired effect with overloaded functions. void func(int x, int y); void func(int x) { func(x, data_); } private: int data_; };
Read more about overloaded functions later in this chapter.
The last parameter can be an ellipsis (...
). The comma that separates the next-to-last parameter from the ellipsis is optional. (If portability with C is important, be sure to include the comma.) The ellipsis permits a variable number of arguments in a function call. See <cstdarg> in Chapter 13 to learn how to access the additional arguments. You can use an ellipsis as the sole parameter, 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, though.
Only non-static member functions (but not constructors or destructors) can have cv-qualifiers (const
and volatile
). They are optional, and if used, they apply to the implicit object parameter of the member function (this
). You can use const
, volatile
, neither, or both in any order. The cv-qualifiers appear after the closing parenthesis of the function parameters. 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 the Overloading section, 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.
The most common use of qualifiers is 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 7 for more information on how cv-qualifiers affect member functions. Example 6-5 shows some simple uses of qualifiers.
Example 6-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(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 is optional, and they are in fact rarely used. The syntax is as follows:
throw (
type-list )
Where the type-list is optional. If present, it is a comma-separated list of type names. (See Chapter 3 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.
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 it 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 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.
The most common use for an exception specification is to mark functions that do not throw exceptions at all (throw()
). Example 6-6 shows various uses of exception specifications.
Example 6-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(); // okay: same as base virtual void g() throw(int); // okay: 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(); // okay }; // Function does not throw any exceptions. int noproblem(int x, int y) throw() try { dostuff(x); dostuff(y); } catch(...) { return 0; } derived* downcast(base* b) throw(std::bad_cast) { return dynamic_cast<derived*>(b); }
Java programmers should note two significant differences between C++ and Java:
throw
, not throws
.Every function must be declared or defined earlier in the source file than where it is used. For functions defined in libraries or other source files, the convention is to declare the function in a .h file and #include
the .h file where the function is used.
A function type includes the language linkage, return type, parameter types, and cv-qualifiers. Note that for each parameter, only its type is significant; its name, storage class, and cv-qualifiers are not part of the function type. The exception specification is not part of the function type.
A single source file can have multiple declarations of the same function type, even if those declarations differ in other ways. For example, one declaration can declare a parameter const
and another might declare it volatile
. The qualifiers are ignored for the function type. They matter only in the function definition, as shown in Example 6-7.
Example 6-7: Declaring and defining functions.
// Three declarations of the same function type int add(const int a, const int b); int add(int x, volatile int); int add(signed int, int signed); // Definition of the function. The parameter qualifiers // in the declarations are irrelevant. Only those in the // definition matter. int add(int x, int y) { return x + y; }
Array and pointer parameters types are also equivalent. In the function body, the parameter is a pointer, not an array. The array size in the parameter declaration is ignored. The first (left-most) size in a multidimensional array is ignored. Similarly, a function parameter is the same as a function pointer parameter. The rules for function types apply recursively to function and function pointer parameters. Thus, a parameter whose type is a pointer to a function that takes an int
is equivalent to one where the function parameter has a const
int
parameter. Example 6-8 illustrates equivalent pointer types.
Example 6-8: Equivalent pointer types.
int first(const int array[10]); // size is ignored int first(const int array[]); // equivalent declaration int first(const int* array); // equivalent declaration int first(const int* array) // definition { return array[0]; } int apply(int func(int), int arg); int apply(int func(const int), int); // equivalent int apply(int (*func)(int), int arg); // equivalent int apply(int (*func)(const int), int arg) // definition { return func(arg); }
Because typedef
declarations do not create new types, but only synonyms for existing types, parameters that differ only in the use of typedef
types are equivalent, as shown in Example 6-9.
Example 6-9: Using typedef in equivalent parameter types.
typedef int INT; typedef const int CINT; void func(int); void func(INT); // equivalent void func(const INT); // equivalent void func(CINT); // equivalent void func(signed int i) // definition { std::cout << i; }
You can declare a function type from a typedef
, but you cannot use the typedef
in a function definition. For example,
typedef int func(int, int); func add; // declares int add(int, int); int add(int a, int b) // cannot use "func add" here { return a + b; }
Be careful to distinguish between a function type and a function pointer. A function can be implicitly converted to a function pointer, and a function can be called using a function pointer. A function pointer, however, cannot be used to declare a function. For example,
typedef func* funcptr; // pointer-to-function type funcptr a = add; // pointer-to-function object int i = a(1, 2); // call the function that a points to
A function signature is similar to a function type, but it ignores the return type. Overload resolution relies on function signatures to determine which overloaded function to call. Read more about overloading later in this chapter.
A function definition consists of a function declaration followed by a function body. The function body has one of two forms:
try
ctor-initializers compound-statement handlerstry
block that includes the constructor initializers. If an exception is thrown from any of the ctor-initializers
, it is handled by the handlers in the same manner as any exception thrown from the compound statement. See Chapter 7 for more information about constructor initializers.try
statement around the entire function body. Example 6-10 shows this use of a try
function body.Example 6-10: Using a try function body.
void func1() try { // statements } catch (...) { // handler } void func2() { try { // statements } catch(...) { // handler } }
A single function name can have multiple declarations. Two declarations with the same function type declare the same function. Declarations with different function types but the same name overload the function name with multiple functions. A function call to an overloaded function requires the compiler to resolve the overloaded name and decide which function to call. It uses the argument types (but not the return type) to resolve overloaded functions. Example 6-11 shows simple examples of overloaded functions. This section explains the rules for overloading and resolution.
Example 6-11: Overloading functions.
int sqrt(int); double sqrt(double); int main() { std::cout << sqrt(3) << '\n'; // sqrt(int) std::cout << sqrt(3.14) << '\n'; // sqrt(double) }
Anytime you declare more than one function with the same name in the same scope, you are overloading the function name. The function can be an ordinary function, member function, constructor, or overloaded operator.
Overloaded functions must differ in their parameter lists: they must have a different number of parameters, or the parameter types must be different. Refer to the Function Types section, earlier in this chapter, for information on equivalent parameter types.
Default arguments are not considered when declaring overloaded functions (but are important when resolving a call to an overloaded function, as described in the next section). Example 6-12 shows some overloaded functions.
Example 6-12: Overloading functions.
class point { public: point(int x = 0); // overloaded constructors point(int x, int y); point(const point& pt); // x and y are overloaded int x() const { return x_; } void x(int newx) { x_ = newx; } int y() const { return y_; } void y(int newy) { y_ = newy; } private: int x_, y_; }; // add is overloaded int add(int, int); int add(int); double add(double, double); double add(double); long add(long, long); long add(long); // The following declarations are not overloaded, but // are redeclarations of the add functions int add(int a = 0, int b = 0); long add(signed long, long signed); long add(int, int); // error: cannot overload on return type namespace ns { // The following is a different add function, in a // different scope. int add(int, int); }
A function call expression (Chapter 4) must identify the function being called, based on its name and arguments. For example, the simple expression f(x)
might be a call to a function named f
, a function call through a function pointer named f
, a function template named f
, the construction of an object of class f
, a conversion to a type named f
, or the invocation of the function call operator of an object named f
. In each situation, the compiler might use different rules for interpreting f
and x
.
The compiler first uses the function name and context to create a list of candidate functions. The number and types of the arguments is used to select the best match from the candidates, and that function is the one that is called. If no match is found, the compiler reports an error. If more than one match is "best," the compiler reports an ambiguity error.
For example, the C++ standard library declares three different sqrt
functions (see <cmath> in Chapter 13):
float sqrt(float); double sqrt(double); long double sqrt(long double);
Suppose you added some other functions named sqrt
, such as the following to apply sqrt
to each element of a sequence:
template<typename InIter, typename OutIter> OutIter sqrt(InIter first, InIter last, OutIter result);
In a function call to sqrt
(say, sqrt(x)
), the compiler first collects all the overloaded sqrt
functions. That's the candidate list, which has four elements in this case. The list is then pruned to eliminate functions with the wrong number of arguments. In this case, the sqrt
function template is eliminated because the expression has only one argument. Finally, the type of x
is used to determine which function to call: if there is an exact match, the corresponding function is called. If x
is, say, an integer, the compiler reports an error because the three floating-point sqrt
functions look equally good. If x
has class type, and the class has a conversion function to one of the floating point types, the compiler implicitly calls that conversion and then calls the corresponding function. For example,
struct FixedPoint { ... operator long double(); }; void demo() { FixedPoint x; std::cout << sqrt(x) << '\n'; // prints a long double }
If a candidate is a function template, the compiler deduces the argument types (see Chapter 8), and from that point on, the template instance is treated as a normal function.
The rules for creating the candidate list and argument list depend on the context of the function call. The argument list can also depend on context: when choosing an overloaded function to call, the member function's class is treated as an argument type.
More precisely, member functions have an implicit object parameter whose type is reference to T, where T is the class that defines the member function. Any qualifiers on the member function also qualify the type (that is, the object type for a const
function is const
T&
). In the function body, the type of this
is pointer to qualified T. (See Chapter 7 for information about this
.)
A call to a member function applies to a specific object, which is the implicit object argument. With the ->
and .
operators, the implicit object argument is the left operand. Inside a nonstatic member function, the implicit object argument is this
. The implicit object argument is considered the first argument in the argument list. Unlike normal arguments, implicit type conversions do not take place for the implicit object argument.
Table 6-1 summaries the various forms of function calls, and the following sections provide the details.
Function Call Syntax | Candidate Functions |
---|---|
expr . name ( args )expr -> name ( args ) | Member functions |
name ( args )expr ( args ) | Member functionsNon-member functions |
expr op expr | Member and non-member functions |
type name ( args ) | Constructors |
type name = expr | ConstructorsType conversion operators |
An expression that uses the .
or ->
operator to call a function must call a member function. The function name is looked up in the class of the left operand, and in its base classes (using the name lookup rules listed in Chapter 3). When a matching name is found, all matching names in that class are the candidate functions.
In other words, a derived class does not overload functions in a base class, but hides them. For a derived class to add an overloaded name to names inherited from the base class, use a using
declaration in the derived class, as shown in Example 6-13.
Example 6-13: Overloading inherited functions.
#include <iostream> #include <ostream> class base { public: void f(int) { std::cout << "f(int)\n"; } void g(int) { std::cout << "g(int)\n"; } }; class derived : public base { public: void f(double) { std::cout << "f(double)\n"; } void g(double) { std::cout << "g(double)\n"; } using base::g; // g(int) and g(double) are visible }; int main() { derived d; d.f(3); // calls derived::f(double) d.g(42); // calls base::g(int) };
An ordinary-looking function call can be a non-member function, a member function, an object of class-type, a type name, or a variable of type pointer-to-function. For a variable of type pointer-to-function, overload resolution takes place when a value is assigned to the variable, which is discussed later in this chapter. In the other cases, the usual name lookup rules apply to find the candidate functions.
A function call in a member function searches first for matching member functions in the same class or an ancestor class. If a match is found, candidate functions are taken from the same class. If no matches are found, outer namespaces are searched for non-member functions. Example 6-14 shows how a matching member function in a base class precludes finding a better match in the global namespace.
Example 6-14: Finding candidate member functions.
#include <iostream> #include <ostream> void proc(int x) { std::cout << "proc(int:" << x << ")\n"; } class base { public: void f(int) { std::cout << "f(int)\n"; } void g(int) { std::cout << "g(int)\n"; } void proc(double) { std::cout << "base::proc(double)\n"; } }; class derived : public base { public: void f(double) { std::cout << "f(double)\n"; } void g(double x) { std::cout << "g(double)\n"; proc(42); // calls base::proc(double), not ::proc(int) } using base::g; }; // Declared after derived, so call to proc() inside g() // never sees this proc(). void proc(double x) { std::cout << "proc(double:" << x << ")\n"; } int main() { derived d; d.g(3.14159); // calls g(double) }
If the function call expression resolves to an object of class type, the class must have a function call operator or a conversion operator, where the conversion is to a function type: pointer to function, reference to function, or reference to pointer to function.
In the case of a conversion operator, the compiler constructs a wrapper function so that the conversion function is the first argument. The list of argument types is then the conversion type, followed by the types of the actual arguments. In other words, all the function-type conversion operators participate in overload resolution.
Example 6-15 illustrates using a class-type object as the left operand of a function call.
Example 6-15: Calling a class-type object as a function.
#include <iostream> #include <ostream> typedef void (*strproc)(const char*); void print(const char* str) { std::cout << "const char*:" << str << '\n'; } void print(int x) { std::cout << "int:" << x << '\n'; } void print(double x) { std::cout << "double:" << x << '\n'; } struct simple { void operator()(int x) { print(x); } // print(int) void operator()(double x) { print(x); } // print(double) }; typedef void (*intfunc)(int); typedef void (*dblfunc)(double); struct indirect { operator intfunc() { return print; } // print(int) operator dblfunc() { return print; } // print(double) operator strproc() { return print; } // print(const char*) }; int main() { simple sim; indirect ind; sim(42); // prints "int:42" sim(42.0); // prints "double:42" ind(42); // prints "int:42" ind(42.0); // prints "double:42" ind("forty-two"); // prints "const char*:forty-two" }
If all the operands of an operator have fundamental types, the operator has its built in meaning. If any operand has a user-defined type (class or enumeration), the programmer can overload the operator, and the overloaded operator function is selected according the usual rules of overloaded functions.
If the left operand has class type, the operator can be a non-static member function or non-member function. Otherwise, the function must be a non-member function. The operator function name is formed from the keyword operator
, followed by the operator symbol, e.g., operator[]
, operato
r
++
, operator-
. Unary operators can be member functions with no arguments or non-member functions of one argument. Binary operators are member functions of one argument or non-member functions of two arguments. Postfix increment and decrement operators are different. They are implemented as binary operators where the right operand is an int
. (See Chapter 4 for more information.)
The ->
operator is also different. Although it is a binary operator, it is treated as a unary operator. It must be implemented as a member function, so the function takes no arguments. It returns a pointer or another object that overloads the ->
operator. Ultimately, the overloaded operators must resolve to a pointer of class type, to which the built in ->
operator can be applied.
The candidate functions include all member, non-member, and built in candidate functions. Member functions do not take precedence over non-member functions.
Example 6-16 shows how operators are resolved by considering member functions and global functions, whereas named member functions take precedence over named global functions.
Example 6-16: Calling overloaded operators
class demo { public: demo(int v) : value_(v) {} demo add(const demo& d) const; demo sub(const demo& d) const; demo mul(const demo& d) const; demo operator+(const demo& d) const; demo operator-(const demo& d) const; demo operator*(const demo& d) const; operator int() const { return value_; } private: int value_; }; // Silly examples, but illustrative. demo add(const demo& a) { return a; } demo mul(const demo& a) { return a; } demo div(const demo& a) { return a; } demo operator+(const demo& a, const demo& b) { return a.operator+(b); // force use of member function } demo demo::add(const demo& d) const { return *this + d; // error: calls ::operator+() or // demo::operator+()? } demo demo::sub(const demo& d) const { return this->operator-(d); // member operator } demo demo::mul(const demo& d) const { return ::operator*(*this, d); // global operator } demo demo::operator+(const demo& d) const { return demo(int(*this) + int(d)); } demo demo::operator-(const demo& d) const { return sub(d); // calls demo::sub (recurses infinitely) } demo demo::operator*(const demo& d) const { return ::mul(d); // scope operator to call global mul() }
An object can be initialized using function-like syntax (see Chapter 3). When an object of class-type is so initialized, the candidate functions are constructors of the named class. Example 6-17 shows function-like initializers that call an overloaded constructor.
Example 6-17: Calling an overloaded constructor.
class point { public: point() : x_(0), y_(0) {} point(int x) : x_(x), y_(0) {} point(int x, int y): x_(x), y_(y) {} private: int x_, y_; }; point p1(42); point p2(4, 2); point p3(p1);
An object can be initialized using assignment-like syntax (see Chapter 3). The candidate functions for "T
x
=
i
" are single-argument constructors of T
, and if i
has class type, conversion functions that convert i
to type T
, as shown in Example 6-18.
Example 6-18: Resolving assignment-like initialization.
class point { public: point() : x_(0), y_(0) {} point(int x) : x_(x), y_(0) {} point(int x, int y) : x_(x), y_(y) {} int x() const { return x_; } int y() const { return y_; } private: int x_, y_; }; class dot { public: dot(int x, int y) : center_(point(x, y)) {} dot(const point& center) : center_(center) {} operator point() const { return center_; } private: point center_; }; point p1 = 3; // invokes point(int) constructor point p2 = 4.2; // converts 4.2 to 4, and invokes point(int) dot d1 = p1; // invokes dot(const point&) constructor point p3 = d1; // invokes dot::operator point() and // implicit point(const point&) copy constructor
An object that has non-class type can be initialized with an object of class type. The candidate functions are the conversion functions of that class type. Example 6-19 shows some examples.
Example 6-19: Initializing non-class objects by calling conversion functions.
enum color { red, black }; template<typename T> class rbnode { public: rbnode(const T& value, color c); operator color() const { return color_; } operator T() const { return value_; } private: T value_; color color_; }; rbnode<int> n(42, black); color c = n; int i = n;
Similar to initializing an object of non-class type with an expression of class-type, as described in the previous section, you can initialize a reference to an lvalue that results from a conversion function. Most conversion operators do not return lvalues, but if they do, you can bind the results to references, as shown in Example 6-20.
Example 6-20: Binding references to conversion lvalues.
enum color { red, black }; template<typename T> class rbnode { public: rbnode(const T& value, color c); operator color&() { return color_; } operator T&() { return value_; } private: T value_; color color_; }; rbnode<int> n(42, black); color& c1 = n; int& i1 = n;
When taking the address of a function, the compiler does not have the benefit of an argument list to help resolve overloading. Instead, it collects a list of candidate functions and picks one based on the type required by the context. The context can be an initializer, an assignment, a function parameter, the return value of a function, or an explicit type cast.
Of the potentially matching functions, non-template functions are better than template functions, and more-specialized template functions are better than less-specialized template functions. There must be exactly one remaining function, or else it is an error. Example 6-21 shows simple examples of resolving the addresses of overloaded functions.
Example 6-21: Taking the address of an overloaded function.
int abs(int); long abs(long); double abs(double); template<typename T> T abs(T x); template<typename T, typename U> T abs(T x, U = U()); int main() { int (*intfunc)(int) = &abs; // abs(int) double (*dblfunc)(double) = &abs; // abs(double) float (*fltfunc)(float) = &abs; // abs<float> short (*shrtfunc1)(short, int) = &abs; // abs<short,int> short (*shrtfunc2)(short) = &abs; // abs<short> }
To find the best matching function from the list of candidate functions, the candidate list is first pruned by removing functions with the wrong number of arguments. Then the remaining functions are checked to determine how to convert the actual arguments to the desired parameter types. The function with the simplest conversions wins. This section discusses these two steps.
Once the compiler has assembled its list of candidate functions, it prunes the list by removing functions that have the wrong number of arguments. If the function call has n argument expressions, functions are kept in the list if:
Also, each actual argument must be convertible (using the rules described in the next section) to the parameter type.
Of the remaining candidate functions, the "best" function is the one that is called. If more multiple functions are tied for best, it is an error. The best function is found by comparing the implicit type conversion sequences needed for each argument type (and possibly the implicit object argument). As described in the next section, some sequences are better than others. When comparing two functions, the better function is chosen as follows:
An actual argument is implicitly converted to the desired parameter type by undergoing a series of transformations. In rough terms, a better conversion is one where the argument type is closer to the parameter type, and therefore, the type undergoes fewer transformations.
A built in conversion sequence is better than a user-defined conversion sequence. A user-defined conversion sequence is better than matching an ellipsis parameter.
Among built in transformations, value-preserving transformations are better than those that might discard information. The transformations are as follows, in order of best to worst:
const
or volatile
qualifiers to match the parameter.bool
is worse than other conversions.void*
.void*
is better than converting a base class pointer to void*
.A
inherits from B
and B
inherits from C
, A*
to B*
is better than A*
to C*
.A user-defined conversion sequence is a built in conversion sequence , followed by a user-defined conversion (constructor or type conversion operator), followed by a built in conversion sequence.
If one sequence of transformations is a subsequence of another, the shorter sequence is better than the longer one.
Among sequences that differ only by qualification, the one with fewer qualification adjustments is better than one with more adjustments.
Example 6-22 shows how overloaded functions are chosen. Notice that type promotions are preferred over the conversion to Num
, even though Num
has an exact type conversion to long
. Also, note how an unsigned
long
cannot be promoted, so it must undergo a built in conversion. The compiler has no preference of int
, long
or double
, so the conversion is ambiguous and is therefore an error. The same applies to long
double
; even though a human might consider the conversion to double
to be "better" than conversion to int
, the rules of overloading say otherwise.
Example 6-22: Resolving overloaded functions.
#include <string> void func(double); void func(long); void func(int); void func(const std::string&); class Num { public: Num(int i) : num_(i) {} operator long() const { return num_; } private: int num_; }; int main() { short n = 42; func(n); // func(int) func(Num(n)); // func(long) func(true); // func(int) func(3.1415f); // func(double) func("string"); // func(string); std::string s("string"); func(s); // func(string); func(3.14159L); // error func(42UL); // error func(L"widestr"); // error }
Overloading functions can have the same effect as using default arguments. The question is when to use overloading and when to use default arguments. As usual, the answer is, "It depends."
If the default argument is complicated, you are probably better off using overloading. Using default arguments, the complicated code would be duplicated at every function call. With overloading, you can concentrate it at a single point.
If the function is complicated, you might want to use default arguments, so you can write the function only once. Even if you use overloading, write a single base function, and let the overloaded functions call the base function. For example,
class point { public: // Using default arguments keeps the code // simple because there is only // one constructor. point(int x = 0, int y = 0); ... // set(1) sets both x and y to the same // value. This is difficult to do with // default arguments, so use overloading. void set(int x) { set(x, x); } void set(int x, int y); };
For user-defined types (classes and enumerations), you can define alternate behavior for the operators, which is called overloading the operators. You cannot define new operator symbols, and not all operator symbols can be overloaded. Table 6-2 lists all the operator symbols, indicates which ones can be overloaded, and if so, shows whether the overload must be a non-static member function.
Operator | Meaning | Overloading permitted? | Must be member function? |
---|---|---|---|
+ |
Addition, unary plus | yes | no |
& |
Address of | yes | no |
[] |
Array subscript | yes | yes |
&= |
Assign bitwise and | yes | no |
^= |
Assign bitwise exclusive or | yes | no |
|= |
Assign bitwise or | yes | no |
-= |
Assign difference | yes | no |
<<= |
Assign left shift | yes | no |
= |
Assignment | yes | yes |
*= |
Assign product | yes | no |
/= |
Assign quotient | yes | no |
%= |
Assign remainder | yes | no |
>>= |
Assign right shift | yes | no |
+= |
Assign sum | yes | no |
& |
Bitwise and | yes | no |
~ |
Bitwise complement | yes | no |
^ |
Bitwise exclusive or | yes | no |
| |
Bitwise or | yes | no |
? : |
Conditional | no | N/A |
new |
Create dynamic object | yes | no |
new[] |
Create dynamic array | yes | no |
-- |
Decrement | yes | no |
delete |
Destroy dynamic object | yes | no |
delete[] |
Destroy dynamic array | yes | no |
/ |
Division | yes | no |
== |
Equal | yes | no |
() |
Function call | yes | yes |
> |
Greater than | yes | no |
>= |
Greater than or equal | yes | no |
++ |
Increment | yes | no |
<< |
Left shift | yes | no |
< |
Less than | yes | no |
<= |
Less than or equal | yes | no |
&& |
Logical and | yes | no |
! |
Logical complement | yes | no |
|| |
Logical or | yes | no |
.* |
Member reference | no | N/A |
->* |
Member reference | yes | yes |
. |
Member reference | no | N/A |
-> |
Member reference | yes | yes |
* |
Multiplication, dereference | yes | no |
!= |
Not equal | yes | no |
% |
Remainder | yes | no |
>> |
Right shift | yes | no |
:: |
Scope | no | N/A |
, |
Serial evaluation | no | N/A |
- |
Subtraction, negation | yes | no |
An overloaded operator is a function, where the function name has the form operator
, followed by the operator symbol, or in the case of a type conversion member function, a type specifier. For example,
enum logical { no, maybe, yes }; logical operator !(logical x); logical operator &&(logical a, logical b); operator bool(logical x);
Some overloaded operators must be member functions, and others can be member or non-member functions (as shown in Table 6-2). When you define a member function, the object is always the left-hand operand. A unary operator, therefore, takes no arguments. A binary operator takes one argument: the right-hand operand. For a non-member function, a unary operator takes one argument and a binary operator takes two arguments: the first argument is the left-hand operand, and the second is the right-hand operand.
Use overloaded operators the same way you use the built in operators. You can also use function notation, where the function name is operator
followed by the operator symbol. You can use the function notation with built in operators, too. For example,
operator-(42, 10) // same as 42 - 10 operator-(33) // same as -33
The usual rules for resolving overloaded functions applies to overloaded operators. The only difference is that the built in operators are also considered candidates. Remember that you cannot overload operators where all the operands have fundamental types. At least one operand must have a user-defined type (class or enumeration).
When overloading binary operators, consider whether the operator should be commutative (a + b is the same as b + a), and if so, you might need to define two overloaded operators. For example,
enum priority { idle, low, normal, high }; // Adding an integer to a priority commutes, // e.g., setpriority(priority() + 2); // setpriority(2 + priority()); priority operator+(priority p, int i); priority operator+(int i, priority p); // Subtracting an integer from a priority does // not commute, e.g., // setpriority(priority() - 1); priority operator-(priority p, int i);
A key difference between the overloaded operators and the built in operators is that the logical &&
and ||
operators are short-circuit operators. If the expression result is known by evaluating only the left operand, the right operand is never evaluated. For overloaded operators, all operands are evaluated before the function is called, so short-circuit evaluation is impossible.
When overloading the increment and decrement operators, they have two forms: prefix and postfix. To distinguish the two forms, the postfix form takes an additional int
argument. Example 6-23 shows one way to overload the increment operator. (Decrement is analogous.)
Example 6-23: Overloading the increment operator.
enum status { stopped, running, waiting }; status& operator++(status& s) { // prefix if (s != waiting) s = status(s + 1); return s; } status operator++(status& s, int) { // postfix status rtn = s; ++s; return rtn; } int main() { status s(stopped); ++s; // calls operator++(s); s++; // calls operator++(s, 0); }
The ->
operator is different from the other operators. Although it is nominally a binary operator, you overload it as a unary operator. It must be implemented as a member function, so the function takes no arguments. It must return one of the following:
->
operatorThe chain of ->
operators is followed until it ends with a pointer to a class type. The actual right operand must name a member of that class. The ->
operator is most often overloaded to implement a smart pointer. See the auto_ptr<>
template in the <memory> section of Chapter 13 for an example.
The function call operator (operator()
) takes any number of arguments. It must be implemented as a member function. To invoke the operator, use an object of class type as the "name" of the function. Pass the arguments the way you would pass any other function arguments. With a simple variable of class type, the syntax looks like an ordinary function call.
An object that overloads the function call operator is often called a functor. Functors are often used with the standard algorithms as a way of preserving state between function calls or just to better encapsulate functionality. See <algorithm> and <functional> in Chapter 13 for examples.
You can overload the global new
and delete
operators, or you can overload the operators of a particular class. If you do not overload the global operators, the C++ library provides an implementation for you. (See <new> in Chapter 13.) If you do not overload the operators for a class, the global operators are used.
If a class overloads the new
and delete
operators, the operator functions are called for new
and delete
expressions involving that class. When overloading a class operator new or delete, you can call the global operator, as shown in Example 6-24.
Example 6-24: Overloading new and delete.
#include <iostream> #include <memory> #include <new> #include <ostream> class singleton { public: void* operator new(size_t size) throw (std::bad_alloc) { std::cout << "singleton::new\n"; if (instance == 0) instance = ::new singleton; ++count; return instance; } void operator delete(void* p) { std::cout << "singleton::delete\n"; if (--count == 0) { ::delete instance; instance = 0; } } static singleton* make() { return new singleton(); } private: singleton() {} singleton(const singleton&); static singleton* instance; static size_t count; }; singleton* singleton::instance; size_t singleton::count; int main() { std::auto_ptr<singleton> s1(singleton::make()); std::auto_ptr<singleton> s2(singleton::make()); return s1.get() == s2.get(); }
If you overload new
, you should probably overload delete
, too.
A class can declare type conversion operators to convert objects of the class type to other types. The operator functions must be non-static member functions. The name of the operator is the desired type, which can be a series of type specifiers and pointer operators, but cannot be a function or array type. For example,
class bigint { public: operator long(); // convert object to type long operator unsigned long(); operator const char*(); // return a string representation ... };
Every program has a function in the global namespace called main
, which is the main program. This function must return type int
. The C++ environment calls main
; your program must never call main
. The main
function cannot be declared inline
or static
. It can be called with no arguments or with two arguments, as follows. (An implementation can permit other parameters.)
int main(int argc, char* argv[])
Where argc
is the number of command line arguments, and argv
is an array of pointers to the command line arguments as null-terminated character strings. By definition, argv[argc]
is a null pointer. The first element of the array (argv[0]
) is the program name or an empty string.
Static objects at namespace scope can have constant or dynamic initial values. Static objects with constant values are initialized by constant data before the program starts. Static objects with dynamic values are initialized by code when the program begins. When, exactly, the objects are initialized is implementation-defined. It might happen before main
is called, or it might be after.
You should try to avoid writing code that depends on the order in which static objects are initialized. If you cannot avoid it, you can work around the problem by defining an initialization class. For example, you can guarantee that the standard I/O stream objects are created early, so they can be used in the constructor of a static object. See the <ios> in Chapter 13 for more information and an example.
Local static objects are initialized when their functions are called. The object is guaranteed to be initialized before the function body starts. If the function is never called, the object is never initialized.
When main
returns or when exit
is called (see <cstdlib> in Chapter 13), static objects are destroyed in the reverse order of their construction, and the program terminates. All local, static objects are also destroyed. If a function that contains a local static object is called during the destruction of static objects, the behavior is undefined.
The value returned from main is passed to the host environment. You can return zero or EXIT_SUCCESS
(declared in <cstdlib>) to indicate success, or EXIT_FAILURE
to tell the environment that the program failed. Other values are implementation-defined. Some environments ignore the value returned from main; others rely on the value.