5.4 Operator Overloading

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

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

yes

no

-

Subtraction, negation

yes

no

type

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:

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).

Defining Commutative Operators

When overloading a binary operator, consider whether the operator
should be commutative (a +
b is the same as b +
a). If that is the case, you might need to
define two overloaded operators:

5.4.1 Short-Circuit Operators

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.

5.4.2 Comma Operator

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.

5.4.3 Increment and Decrement

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.)

5.4.4 Member Reference

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.

5.4.5 Function Call

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.

5.4.6 operator new and operator delete

A new expression (Chapter 3)
calls operatornew to allocate memory, and a
delete expression calls
operatordelete. (A
new[] expression calls operatornew[], and a delete[]
expression calls operatordelete[]. For the sake of simplicity, whenever I
refer to a new expression, I mean a
new expression or new[]
expression. Similarly, operatornew refers to operatornew and operatornew[]. Ditto for delete.)

You can overload operatornew
and operatordelete 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 operatornew and operatordelete functions, the corresponding operator
functions are called for new and
delete expressions involving that class. When
overloading operatornew or
operatordelete as member
functions or with placement arguments, you can call the global
operator, as shown in Example 5-25.

The first parameter to operatornew 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
operatordelete 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 operatornew and operatordelete.

When you overload the operatornew and operatordelete functions, you will probably want to
overload the scalar (operatornew) and array versions
(operatornew[]) 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 operatornew,
you should probably also overload operatordelete. 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 operatordelete 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 operatordelete.

5.4.7 Type Conversion

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: