C++ OPERATOR OVERLOADING

PREVIOUS

OPERATOR OVERLOADING

! "     !    
Q: Name the operators that cannot be overloaded?
A:sizeof, ., .*, .->, ::, ?:
Q: What is overloading??
A: With the C++ language, you can overload functions and operators. Overloading is the
practice of supplying more than one definition for a given function name in the same scope.
- Any two functions in a set of overloaded functions must have different argument lists.
- Overloading functions with argument lists of the same types, based on return type alone, is an
error.
Q: How are prefix and postfix versions of operator++() differentiated?
A: The postfix version of operator++() has a dummy parameter of type int. The prefix version
does not have dummy parameter.
Q: Can you overload a function based only on whether a parameter is a value or a reference?
A: No. Passing by value and by reference looks identical to the caller.
Q: What's the deal with operator overloading?
A: It allows you to provide an intuitive interface to users of your class, plus makes it possible for
templates to work equally well with classes and built-in/intrinsic types.
Operator overloading allows C/C++ operators to have user-defined meanings on user-defined
types (classes). Overloaded operators are syntactic sugar for function calls:
class Fred {
public:
...
};
#if 0
// Without operator overloading:
Fred add(const Fred& x, const Fred& y);
Fred mul(const Fred& x, const Fred& y);
Fred f(const Fred& a, const Fred& b, const Fred& c)
{
return add(add(mul(a,b), mul(b,c)), mul(c,a)); // Yuk...
}
#else
// With operator overloading:
Fred operator+ (const Fred& x, const Fred& y);
Fred operator* (const Fred& x, const Fred& y);
Fred f(const Fred& a, const Fred& b, const Fred& c)
{
return a*b + b*c + c*a;
}
#endif
Q: What are the benefits of operator overloading?
A: By overloading standard operators on a class, you can exploit the intuition of the users of that
class. This lets users program in the language of the problem domain rather than in the language
of the machine.
The ultimate goal is to reduce both the learning curve and the defect rate.
Q: What are some examples of operator overloading?
A: Here are a few of the many examples of operator overloading:
myString + yourString might concatenate two std::string objects
myDate++ might increment a Date object
a * b might multiply two Number objects
a[i] might access an element of an Array object
x = *p might dereference a "smart pointer" that "points" to a disk record  it could seek to the
location on disk where p "points" and return the appropriate record into x
Q: But operator overloading makes my class look ugly; isn't it supposed to make my code
clearer?
A: Operator overloading makes life easier for the users of a class, not for the developer of the
class!
Consider the following example.
class Array {
public:
int& operator[] (unsigned i); // Some people don't like this syntax
...
};
inline
int& Array::operator[] (unsigned i) // Some people don't like this syntax
{
...

Some people don't like the keyword operator or the somewhat funny syntax that goes with it in
the body of the class itself. But the operator overloading syntax isn't supposed to make life easier
for the developer of a class. It's supposed to make life easier for the users of the class:
int main()
{
Array a;
a[3] = 4; // User code should be obvious and easy to understand...
...
}
Remember: in a reuse-oriented world, there will usually be many people who use your class, but
there is only one person who builds it (yourself); therefore you should do things that favor the
many rather than the few.
Q: What operators can/cannot be overloaded?
A: Most can be overloaded. The only C operators that can't be are . and ?: (and sizeof, which is
technically an operator). C++ adds a few of its own operators, most of which can be overloaded
except :: and .*.
Here's an example of the subscript operator (it returns a reference). First without operator
overloading:
class Array {
public:
int& elem(unsigned i) { if (i > 99) error(); return data[i]; }
private:
int data[100];
};
int main()
{
Array a;
a.elem(10) = 42;
a.elem(12) += a.elem(13);
...
}
Now the same logic is presented with operator overloading:
class Array {
public:
int& operator[] (unsigned i) { if (i > 99) error(); return data[i]; }
private:
int data[100];
};
int main()
{
Array a;
a[10] = 42;
a[12] += a[13];
...
}
Q: Can I overload operator== so it lets me compare two char[] using a string comparison?
A: No: at least one operand of any overloaded operator must be of some user-defined type (most
of the time that means a class).
But even if C++ allowed you to do this, which it doesn't, you wouldn't want to do it anyway
since you really should be using a std::string-like class rather than an array of char in the first
place since arrays are evil.
Q: Can I create a operator** for "to-the-power-of" operations?
A: Nope.
The names of, precedence of, associativity of, and arity of operators is fixed by the language.
There is no operator** in C++, so you cannot create one for a class type.
If you're in doubt, consider that x ** y is the same as x * (*y) (in other words, the compiler
assumes y is a pointer). Besides, operator overloading is just syntactic sugar for function calls.
Although this particular syntactic sugar can be very sweet, it doesn't add anything fundamental. I
suggest you overload pow(base,exponent) (a double precision version is in ).
By the way, operator^ can work for to-the-power-of, except it has the wrong precedence and
associativity.
Q: Okay, that tells me the operators I can override; which operators should I override?
A: Bottom line: don't confuse your users.
Remember the purpose of operator overloading: to reduce the cost and defect rate in code that
uses your class. If you create operators that confuse your users (because they're cool, because
they make the code faster, because you need to prove to yourself that you can do it; doesn't really
matter why), you've violated the whole reason for using operator overloading in the first place.
Q: What are some guidelines / "rules of thumb" for overloading operators?
A: Here are a few guidelines / rules of thumb (but be sure to read the previous FAQ before
reading this list):
Use common sense. If your overloaded operator makes life easier and safer for your users, do it;
otherwise don't. This is the most important guideline. In fact it is, in a very real sense, the only
guideline; the rest are just special cases.
If you define arithmetic operators, maintain the usual arithmetic identities. For example, if your
class defines x + y and x - y, then x + y - y ought to return an object that is behaviorally
equivalent to x. The term behaviorally equivalent is defined in the bullet on x == y below, but
simply put, it means the two objects should ideally act like they have the same state. This should
be true even if you decide not to define an == operator for objects of your class.
You should provide arithmetic operators only when they make logical sense to users. Subtracting
two dates makes sense, logically returning the duration between those dates, so you might want
to allow date1 - date2 for objects of your Date class (provided you have a reasonable class/type
to represent the duration between two Date objects). However adding two dates makes no sense:
what does it mean to add July 4, 1776 to June 5, 1959? Similarly it makes no sense to multiply or
divide dates, so you should not define any of those operators.
You should provide mixed-mode arithmetic operators only when they make logical sense to
users. For example, it makes sense to add a duration (say 35 days) to a date (say July 4, 1776), so
you might define date + duration to return a Date. Similarly date - duration could also return a
Date. But duration - date does not make sense at the conceptual level (what does it mean to
subtract July 4, 1776 from 35 days?) so you should not define that operator.
If you provide constructive operators, they should return their result by value. For example, x + y
should return its result by value. If it returns by reference, you will probably run into lots of
problems figuring out who owns the referent and when the referent will get destructed. Doesn't
matter if returning by reference is more efficient; it is probably wrong. See the next bullet for
more on this point.
If you provide constructive operators, they should not change their operands. For example, x + y
should not change x. For some crazy reason, programmers often define x + y to be logically the
same as x += y because the latter is faster. But remember, your users expect x + y to make a
copy. In fact they selected the + operator (over, say, the += operator) precisely because they
wanted a copy. If they wanted to modify x, they would have used whatever is equivalent to x +=
y instead. Don't make semantic decisions for your users; it's their decision, not yours, whether
they want the semantics of x + y vs. x += y. Tell them that one is faster if you want, but then step
back and let them make the final decision they know what they're trying to achieve and you do
not.
If you provide constructive operators, they should allow promotion of the left-hand operand. For
example, if your class Fraction supports promotion from int to Fraction (via the non-explicit ctor
Fraction::Fraction(int)), and if you allow x - y for two Fraction objects, you should also allow 42
- y. In practice that simply means that your operator-() should not be a member function of
Fraction. Typically you will make it a friend, if for no other reason than to force it into the
public: part of the class, but even if it is not a friend, it should not be a member.
In general, your operator should change its operand(s) if and only if the operands get changed
when you apply the same operator to intrinsic types. x == y and x << y should not change either
operand; x *= y and x <<= y should (but only the left-hand operand).
If you define x++ and ++x, maintain the usual identities. For example, x++ and ++x should have
should have the same observable effect on x, and should differ only in what they return. ++x
should return x by reference; x++ should either return a copy (by value) of the original state of x
or should have a void return-type. You're usually better off returning a copy of the original state
of x by value, especially if your class will be used in generic algorithms. The easy way to do that
is to implement x++ using three lines: make a local copy of *this, call ++x (i.e., this-
>operator++()), then return the local copy. Similar comments for x-- and --x.
If you define ++x and x += 1, maintain the usual identities. For example, these expressions
should have the same observable behavior, including the same result. Among other things, that
means your += operator should return x by reference. Similar comments for --x and x -= 1.
If you define *p and p[0] for pointer-like objects, maintain the usual identities. For example,
these two expressions should have the same result and neither should change p.
If you define p[i] and *(p+i) for pointer-like objects, maintain the usual identities. For example,
these two expressions should have the same result and neither should change p. Similar
comments for p[-i] and *(p-i).
Subscript operators generally come in pairs; see on const-overloading.
If you define x == y, then x == y should be true if and only if the two objects are behaviorally
equivalent. In this bullet, the term "behaviorally equivalent" means the observable behavior of
any operation or sequence of operations applied to x will be the same as when applied to y. The
term "operation" means methods, friends, operators, or just about anything else you can do with
these objects (except, of course, the address-of operator). You won't always be able to achieve
that goal, but you ought to get close, and you ought to document any variances (other than the
address-of operator).
If you define x == y and x = y, maintain the usual identities. For example, after an assignment,
the two objects should be equal. Even if you don't define x == y, the two objects should be
behaviorally equivalent (see above for the meaning of that phrase) after an assignment.
If you define x == y and x != y, you should maintain the usual identities. For example, these
expressions should return something convertible to bool, neither should change its operands, and
x == y should have the same result as !(x != y), and vice versa.
If you define inequality operators like x <= y and x < y, you should maintain the usual identities.
For example, if x < y and y < z are both true, then x < z should also be true, etc. Similar
comments for x >= y and x > y.
If you define inequality operators like x < y and x >= y, you should maintain the usual identities.
For example, x < y should have the result as !(x >= y). You can't always do that, but you should
get close and you should document any variances. Similar comments for x > y and !(x <= y), etc.
Avoid overloading short-circuiting operators: x || y or x && y. The overloaded versions of these
do not short-circuit they evaluate both operands even if the left-hand operand "determines" the
outcome, so that confuses users.
Avoid overloading the comma operator: x, y. The overloaded comma operator does not have the
same ordering properties that it has when it is not overloaded, and that confuses users.
Don't overload an operator that is non-intuitive to your users. This is called the Doctrine of Least
Surprise. For example, altough C++ uses std::cout << x for printing, and although printing is
techincally called inserting, and although inserting sort of sounds like what happens when you
push an element onto a stack, don't overload myStack << x to push an element onto a stack. It
might make sense when you're really tired or otherwise mentally impaired, and a few of your
friends might think it's "kewl," but just say No.
Use common sense. If you don't see "your" operator listed here, you can figure it out. Just
remember the ultimate goals of operator overloading: to make life easier for your users, in
particular to make their code cheaper to write and more obvious.
Caveat: the list is not exhaustive. That means there are other entries that you might consider
"missing." I know.
Caveat: the list contains guidelines, not hard and fast rules. That means almost all of the entries
have exceptions, and most of those exceptions are not explicitly stated. I know.
Caveat: please don't email me about the additions or exceptions. I've already spent way too much
time on this particular answer.
    
PREVIOUS

No comments:

Post a Comment