|
|
|
|
Arguments & return values
It may seem a little confusing at first
when you look at OverloadingUnaryOperators.cpp, Integer.h and
Byte.h and see all the different ways that arguments are passed and
returned. Although you can pass and return arguments any way you want to,
the choices in these examples were not selected at random. They follow a logical
pattern, the same one you’ll want to use in most of your
choices.
- As with any function
argument, if you only need to read from the argument and not change it, default
to passing it as a const reference. Ordinary arithmetic operations (like
+ and –, etc.) and Booleans will not change their arguments,
so pass by const reference is predominantly what you’ll use. When
the function is a class member, this translates to making it a const
member function. Only with the operator-assignments (like +=) and the
operator=, which change the left-hand argument, is the left argument
not a constant, but it’s still passed in as an address because it
will be changed.
- The
type of return value you should select depends on the expected meaning of the
operator. (Again, you can do anything you want with the arguments and return
values.) If the effect of the operator is to produce a new value, you will need
to generate a new object as the return value. For example,
Integer::operator+ must produce an Integer object that is the sum
of the operands. This object is returned by value as a const, so the
result cannot be modified as an
lvalue.
- All the
assignment operators modify the lvalue. To allow the result of the assignment to
be used in chained expressions, like a=b=c, it’s expected that you
will return a reference to that same lvalue that was just modified. But should
this reference be a const or nonconst? Although you read
a=b=c from left to right, the compiler parses it from right to left, so
you’re not forced to return a nonconst to support assignment
chaining. However, people do sometimes expect to be able to perform an operation
on the thing that was just assigned to, such as (a=b).func( ); to
call func( ) on a after assigning b to it. Thus, the
return value for all of the assignment operators should be a nonconst
reference to the
lvalue.
- For the
logical operators, everyone expects to get at worst an int back, and at
best a bool. (Libraries developed before most compilers supported
C++’s built-in bool will use int or an equivalent
typedef.)
The increment
and decrement operators
present
a dilemma because of the pre- and postfix versions. Both versions change the
object and so cannot treat the object as a const. The prefix version
returns the value of the object after it was changed, so you expect to get back
the object that was changed. Thus, with prefix you can just return *this
as a reference. The postfix version is supposed to return the value
before the value is changed, so you’re forced to create a separate
object to represent that value and return it. So with postfix you must return by
value if you want to preserve the expected meaning. (Note that you’ll
sometimes find the increment and decrement operators returning an int or
bool to indicate, for example, whether an object designed to move through
a list is at the end of that list.) Now the question is: Should these be
returned as const or nonconst? If you allow the object to be
modified and someone writes (++a).func( ), func( ) will
be operating on a itself, but with (a++).func( ),
func( ) operates on the temporary object returned by the postfix
operator++. Temporary objects are automatically const, so this
would be flagged by the compiler, but for consistency’s sake it may make
more sense to make them both const, as was done here. Or you may choose
to make the prefix version non-const and the postfix const.
Because of the variety of meanings you may want to give the increment and
decrement operators, they will need to be considered on a case-by-case
basis.
|
|
|