For user-defined types (classes and enumerations), you can define alternate behavior for the C++ operators. This is called overloading the operators. You cannot define new operators, and not all operators can be overloaded. Table 5-2 lists all the operators and indicates which can be overloaded. For these, it shows whether the overload must be a member function. Overloaded operators that are implemented as member functions must be nonstatic member functions.
Table 5-2. Operators and overloading
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 |
| Create dynamic object | yes | no |
| Create dynamic array | yes | no |
| Decrement | yes | no |
| Destroy dynamic object | yes | no |
| 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 | yes | no |
| Subtraction, negation | yes | no |
| Type conversion | yes | yes |
An overloaded operator is a function in which the function name
has the form operator
followed by the
operator symbol, or in the case of a type conversion member function, a
list of type specifiers (with pointer, reference, and array operators).
For example, the following code declares functions to overload the
!
and &&
operators:
enum logical { no, maybe, yes }; logical operator !(logical x); logical operator &&(logical a, logical b);
Some overloaded operators must be member functions, and others can be member or nonmember functions (as shown in Table 5-2). When you define a member function, the object is always the lefthand operand. A unary operator, therefore, takes no arguments because the one operand is the object itself. Likewise, a binary operator takes one argument: the righthand operand; the lefthand operand is the object. For a nonmember function, a unary operator takes one argument and a binary operator takes two arguments: the first argument is the lefthand operand, and the second is the righthand operand.
Use overloaded operators as you would built-in operators. You can
also use function notation, in which the function name is operator
followed by the operator symbol, but
this usage is uncommon. You can use the function notation with built-in
operators, too, but such usage is extremely uncommon. 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 added to the list of candidates along with the user-defined operators. Remember that you cannot overload operators when all the operands have fundamental types. At least one operand must have a user-defined type (class or enumeration).
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 a function is called, so
short-circuit evaluation is impossible.
In other words, you cannot tell whether the &&
and ||
operators perform short-circuit
evaluation by merely glancing at them. You must study the types of the
operands. It is safest, therefore, never to overload these operators.
If the operators are never overloaded, you know that they are always
short-circuit operators.
You can overload the comma operator (,), but you will rarely have any reason to do so. If you do, however, you change its semantics in a subtle way. The built-in comma operator has a sequence point (see Chapter 3) between its operands, so you know that the lefthand expression is completely evaluated before the righthand expression. If you overload the operator, you lose that guarantee. The ordinary rules apply, so the operands might be evaluated in any order.
When overloading the increment and decrement operators, remember that they
have two forms: prefix and postfix. To distinguish between the two
forms, the postfix form takes an additional int
parameter. The compiler always passes
0
as the additional argument. Example 5-24 shows one way to
overload the increment operator. (Decrement is analogous.)
Example 5-24. 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 you use it as 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:
An object of class type, for which the type overloads the
->
operator
A pointer to a class type
A 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 as you would 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 typically used with the standard
algorithms to better encapsulate functionality. Some algorithms, such
as for_each
, also permit the
functor to store state information, which cannot be done with a plain
function. Comparison functions for the associative containers are
easier to implement as functors. See <algorithm>
and <functional>
in Chapter 13 for examples.
A new
expression (Chapter 3) calls operator
new
to allocate
memory, and a delete
expression
calls operator
delete
. (A new[]
expression calls operator
new[]
, and a delete[]
expression calls operator
delete[]
. For the sake of simplicity,
whenever I refer to a new
expression, I mean a new
expression
or new[]
expression. Similarly,
operator
new
refers to operator
new
and operator
new[]
. Ditto for delete
.)
You can overload operator
new
and operator
delete
in the global scope or as members of
a class. In the global scope, the functions must not be static
, nor can you declare them in a
namespace. When you define these operators as member functions, the
functions are always static
, even
if you omit the static
specifier.
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 operator
new
and operator
delete
functions, the corresponding operator
functions are called for new
and
delete
expressions involving that
class. When overloading operator
new
or operator
delete
as member functions or with placement
arguments, you can call the global operator, as shown in Example 5-25.
Example 5-25. Overloading operator new and operator delete
#include <cstddef> #include <iostream> #include <memory> #include <new> #include <ostream> class demo { public: static void* operator new(std::size_t size) throw (std::bad_alloc) { std::cout << "demo::new\n"; if (instance == 0) instance = ::new demo; ++count; return instance; } static void operator delete(void* p) { std::cout << "demo::delete\n"; if (--count == 0) { ::delete instance; instance = 0; } } static demo* make( ) { return new demo( ); } private: demo( ) {} demo(const demo&); static demo* instance; static std::size_t count; }; demo* demo::instance; std::size_t demo::count; int main( ) { std::auto_ptr<demo> s1(demo::make( )); std::auto_ptr<demo> s2(demo::make( )); return s1.get( ) == s2.get( ); }
The first parameter to operator
new
has type size_t
and is the amount of memory to
allocate. The function returns a pointer to the allocated memory as a
void*
. Additional parameters are
allowed for placement new
functions
(see Chapter 3). The first
parameter to operator
delete
is a void*
pointer to the memory; the function
returns void
. Additional parameters
are allowed for placement delete
.
See <new>
in Chapter 13 for more information about
overloaded operator
new
and operator
delete
.
When you overload the operator
new
and operator
delete
functions, you will probably want to
overload the scalar (operator
new
) and array versions (operator
new[]
) of the operator. The scalar and array
versions often behave identically, but you have the option of making
them behave differently. Note that the compiler initializes the
objects, so your allocation or deallocation function does not need to
know the number of objects being allocated or freed.
If you overload operator
new
, you should probably also
overload operator
delete
. In the case of placement new
, the corresponding placement delete
function is called if an exception is
thrown while constructing the newly allocated object or array. Without
a corresponding placement delete
function, no operator
delete
function is called. This is the only
time a placement delete
function is
called. A delete
expression always
calls the plain, single-argument form of operator
delete
.
A class can declare type conversion operators to convert class-type objects to other types. The operator functions must be nonstatic member functions. The name of each operator is the desired type, which can be a series of type specifiers with pointer, reference, and array operators, but cannot be a function or array type:
class bigint { public: operator long( ); // Convert object to type long. operator unsigned long( ); operator const char*( ); // Return a string representation ... };