Lecture 6: Operator Overloading
Lecture 6: Operator Overloading
6.1 It's All Notation
Really all that's happening is a notational trick. By giving a member or non-member function a special name, we indicate to the compiler that it should consider it when it runs into the use of a particular operator involving a class. When the compiler is looking at a+b it's as if it saw a.operator+(b) or operator+(a,b). +a can be interpreted as a.operator+() or operator+(a).
A non-member function implementation of a binary operation has the additional flexibility of being able to apply conversions to the first argument.
References are commonly used as arguments. This is where the reference syntax becomes valuable since it allows the usual operator notation while providing the efficiency that comes with passing pointers to classes instead of (possibly constructed) copies. Be careful when returning references though. Don't use references to local objects which are destroyed when the function exits.
6.2 General Restrictions
The usual rules of function call overloading apply, plus it must be remembered that the built-in operators are prospective candidates for resolving any call, and there are the following general restrictions:
- The standard C++ precedences and associativities determine how any expression will be parsed and can't be changed.
- No new operators can be invented, e.g. @ can't be overloaded as an operator.
- The operator must be written as unary or binary depending on its use with the built-in types, e.g. a binary operator can't be overloaded in a unary way for a class if there's no built-in type for which it's used in a unary manner.
- For non-member functions overloading operators, at least one function argument must be an instance or a reference to a class. This ensures no C++ code can change the built-in behavior of operators with built-in types.
- No default values.
- There are also operator-specific restrictions, notations and features which are enumerated below.
The following operators must be implemented as member functions:
= -> () []
6.3 Guidelines
The good news is that the programmer has complete control over the return type and argument types of the function. This flexibility must be used with some sensitivity. For example, + should be overloaded for a class only when replacing functional notation for an operation analagous to addition for the class. Unless there is a good reason for doing otherwise, it should mimic the behavior of the built-in operator as much as possible meaning (for a function overloading +):
- It shouldn't change the value of either operand, since + doesn't do that when applied to built-in types.
- It should be commutative.
Each operator has a sort of "culture", a way programmers expect it to behave given their experience with built-in types. The more a new version of the operator fits in with this experience, the easier it will be for the programmer to utilize it in an effective and error-free manner. In addition, the class will be more likely to fit into and work properly in templates that require the operator.
6.4 Operator-specific restrictions, notations and features
Copy assignment operator
One operator which frequently needs to be explicitly defined for a safe
class interface is the copy assignment operator. This is because, if one is not
defined explicitly, the compiler generates a default version which does a
member-wise copy. The copy assignment operator has a reference or value of the
same type as its argument. This "feature" is for backward
compatibility with C. It can have the same terrible problems in conjunction with dynamically allocated members as the copy constructor does.
In designing assignment operation, the effects of assigning an object to itself must be taken into account. This may seem silly at first glance, but it is a very real possibility when indirection or references are being used.
Other assignment operators
Behavior of overloaded += is
independent (and must be defined separately and explicitly to be used) from the
behavior of overloaded + and =. This applies to the rest of the
assignment operators as well. It often makes sense to define binary + using the (usually simpler) += operation. += will usually be faster since it doesn't require
construction of a temporary to hold the final result since the final result is
stored in an existing variable.
operator++
and operator--
These two unary operators have prefix and postfix versions. The prefix
version is implemented as with the other unary operators. The postfix version
must be implemented as a.operator++(int)
or operator++(a, int). The int
argument is a dummy which is not used for anything. Incidentally, in C++, the assignment operators and prefix ++ and -- result in l-values contrary to their behavior in C. Again, overloaded versions of these operators should behave as the built-in types if not overly inconvenient. Here inefficiency can be a big concern as the built-in behavior for the postfix version of these operators is to return by value a snap shot preserving the state of the object before it is operated on. For a large object where copy construction is expensive, this could be prohibitive.
operator()
function call
Overloading this enables instances of the class to be treated as if they
were function identifiers. It can be overloaded several times for varying
argument lists. Can be very useful.
operator->
Overloaded versions of this operator could well be termed "pass the
buck" operators. This is implemented as a unary member function and allows
-> to be applied to an
instance of a class, whereas the built-in version always requires a pointer to
a class or struct. A name of a data or function member must appear on the right side of the operator. If the operator returns a pointer to an object, that data or function access must make sense for the object. If the operator returns a reference or instance of a class, that class must in turn have the -> operator overloaded. Thus, one usage of this operator could potentially result in many functions being called if the buck is passed extensively, though in practice it is usually just one layer deep.
This is useful for smart pointers. A program can defer construction of an object or loading of data until access makes it necessary.
new
and delete
The sort of overloading which can be done here is restricted to determining
where run-time memory is obtained when an object is created via these
operators. Typically they are used to optimize heap memory allocation for a
class. For example, if a Tree
class created many instances of a Node
class, it may be more economical to allocate them in batches, or to check a
list of "used" nodes before trying to allocate a new one. The format of the delete member function is
void operator delete(void *, size_t)
The second argument may be omitted.
The format of the new member function is
void *operator new(size_t)
Additional arguments of arbitrary type may be added after the initial size_t type. If these arguments appear, they are supplied after the new invocation in function call format, i.e. new(...args...) MyClass. It always returns a void * type. Even so, the expression where it is applied has the type of a pointer to the type which is being allocated as is the case with the built-in operator.
Conversion functions
casts (and conversions in general via assignment, C-style cast, C++ style
cast) can be "overloaded" as well, but it is conceptually distinct
since the return type is fixed (can't be programmer-defined as in general case)
and they can only be defined as member functions, not friends. It looks similar
though. An int conversion
function is defined as the member function operator
int(). It has no return value and no arguments. If a conversion
function is to be written for a complex type, a typedef must be used so it can be manipulated as a single
word. The array of needed implementations of an operator can be reduced by defining conversions for the classes involved. Conversions to a class are defined implicitly with single-argument constructors. Conversions from the class are defined using conversion functions and exist by virtue of polymorphism.
0 comments: