NOTE: This document may be circulated or quoted from freely, as long as the copyright credit is included.
// Weight class
// ------------
#ifndef WEIGHT
define WEIGHT const Weight
class Weight {
long pounds;
short ounces;
public:
.
.
// Relational operators
// --------------------
friend bool operator==(WEIGHT ls, WEIGHT rs)
(return ls.pounds==rs.pounds && ls.ounces==rs.ounces;}
friend bool operator!=(WEIGHT ls, WEIGHT rs)
(return ls.pounds!=rs.pounds || ls.ounces!=rs.ounces;}
friend bool operator<(WEIGHT ls, WEIGHT rs)
(return ls.pounds < rs.pounds
|| (ls.pounds== rs.pounds && ls.ounces < rs.ounces ;}
friend bool operator<=(WEIGHT ls, WEIGHT rs)
(return ls.pounds < rs.pounds
|| (ls.pounds== rs.pounds && ls.ounces <= rs.ounces ;}
friend bool operator>(WEIGHT ls, WEIGHT rs)
(return ls.pounds > rs.pounds
|| (ls.pounds== rs.pounds && ls.ounces > rs.ounces ;}
friend bool operator>=(WEIGHT ls, WEIGHT rs)
(return ls.pounds > rs.pounds
|| (ls.pounds== rs.pounds && ls.ounces >= rs.ounces ;}
.
.
};
#endif
|
Quite a lot!
The six relational operator definitions contain a lot of detail, much of it repetitive. If a student turned in such a class definition as part of an assignment, I would pose to him or her these two questions:
(16*ls.pounds + ls.ounces) < (16*rs.pounds + rs.ounces)
Readers of these pages know that we consider unnecessary
repetition one of the major faults in
software quality. Those relational
operator functions are so similar to one another that there must be a way to
eliminate some repetition. An obvious place to start is the non-equals operator,
which can be expressed as the negation of the equals operator:
friend bool operator!=(WEIGHT ls, WEIGHT rs) (return ! (ls==rs);}
But that function doesn't need to be a friend
of the Weight class, since it uses no private
members. It can be specified after the class definition in the same
#include file:
inline bool operator!=(WEIGHT ls, WEIGHT rs) (return ! (ls==rs);}
By similar reasoning we note that the <=,
>, and
>= operator functions can be defined
like this:
inline bool operator> (WEIGHT ls, WEIGHT rs) (return rs < ls;}
inline bool operator<=(WEIGHT ls, WEIGHT rs) (return !(ls > rs);}
inline book operator>=(WEIGHT ls, WEIGHT rs) (return rs <= ls;}
So, we see that if we define the
== and
< aoperators as
primitive, we can define the other four relational operators in terms of them.
That yields much more pleasing answers to the two questions we posed earlier. There's
a lot less code to modify if we change our mind, and there's a lot less code to
test.
In pondering the original question, "What's wrong with this class?"
you probably noted that we had chosen a really stupid internal representation
for a Weight object. Mixed-base numeric
units, such as pounds-and-ounces, may be familiar to some users, but they
introduce a lot of complexity in what should be simple.
Therefore, let's change the private internal representation to a simple unit (grams
or, if you prefer, ounces),
probably floating point, call it value, and
simplify the two basic relationals to this:
friend bool operator== (WEIGHT ls, WEIGHT rs)
(return ls.value == rs.value;}
friend bool operator< (WEIGHT ls, WEIGHT rs)
(return ls.value < rs.value;}
What do we now have to do about the other four relational operators? Nothing! If the
above two methods are correct, then they're all correct. Simplifying the overloaded operator functions made it much easier to change the internal data representation.
This change to a simpler data representation will also simplify other methods, especially the overloaded arithmentic operators.
Note that the bodies of the four derived functions know nothing at all about
the internal structure of the objects. They refer to no private data. They
operate only on their parameters ls
and rs.
In fact the bodies of the four derived relational operators would be exactly the
same for any class for which ordering
(<) and equality
(==) are defined. The only difference
would be the types of the declared parameters.
But that's exactly what function templates are for. If we define the four
derived relationals as function templates, like this:
template <type=T>
inline bool operator!=(T& ls, T& rs) {return !(ls == rs); }
template <type=T>
inline bool operator> (T& ls, T& rs) (return rs < ls;}
template <type=T>
inline bool operator<=(T& ls, T& rs) (return !(ls > rs);}
template <type=T>
inline book operator>=(T& ls, T& rs) (return rs <= ls;}
They should work for any class that defines equality (==) and ordering
(<), and we need never think about defining them again for any new
class. There remeain, however, a couple of choices to be decided:
Anywhere that the compiler will see them. At IDI we collect things that
every compilation is likely to need—as if they were extensions to
the C++
language— in an #include file
"global.hpp".
By all means. There may be some situations where you wouldn't want the template version, but you can always define your specialized version. If you learn of a situation where this might lead to trouble, please let me know, so I can amend this advice.
Apparently not. That's why I put this article in the programming peeves section. In programs written by clients and by software vendors and even in a few textbooks, we often encounter:
That's simply poor practice, and should count as a strong indication of programmer naïveté.
Two common reasons lie behind this flaw.
< for his own use. and, thinking
he was following the advice of agile extremists
didn't bother providing >, etc.
operator<. The
situation is:
<
comparisons or any of the other ordering relationals.
operator< for some
sort of quasi ordering.
operator< a
private method and conferring
friend status on the library
class.
This article was inspired by a discussion in the Chicago C++ User Group.
Return to IDI Home Page
Return to Technical articles
Last modified 4 February 2010