14.3. Arithmetic and Relational Operators
Ordinarily, we define the arithmetic and relational operators as nonmember functions in order to allow conversions for either the left- or right-hand operand (§ 14.1, p. 555). These operators shouldn’t need to change the state of either operand, so the parameters are ordinarily references to const
.
An arithmetic operator usually generates a new value that is the result of a computation on its two operands. That value is distinct from either operand and is calculated in a local variable. The operation returns a copy of this local as its result. Classes that define an arithmetic operator generally define the corresponding compound assignment operator as well. When a class has both operators, it is usually more efficient to define the arithmetic operator to use compound assignment:
// assumes that both objects refer to the same book
Sales_data
operator+(const Sales_data &lhs, const Sales_data &rhs)
{
Sales_data sum = lhs; // copy data members from lhs into sum
sum += rhs; // add rhs into sum
return sum;
}
This definition is essentially identical to our original add
function (§ 7.1.3, p. 261). We copy lhs
into the local variable sum
. We then use the Sales_data
compound-assignment operator (which we’ll define on page 564) to add the values from rhs
into sum
. We end the function by returning a copy of sum
.
TIP
Classes that define both an arithmetic operator and the related compound assignment ordinarily ought to implement the arithmetic operator by using the compound assignment.
INFO
Exercises Section 14.3
Exercise 14.13: Which other arithmetic operators (Table 4.1 (p. 139)), if any, do you think Sales_data
ought to support? Define any you think the class should include.
Exercise 14.14: Why do you think it is more efficient to define operator+
to call operator+=
rather than the other way around?
Exercise 14.15: Should the class you chose for exercise 7.40 from § 7.5.1 (p. 291) define any of the arithmetic operators? If so, implement them. If not, explain why not.
14.3.1. Equality Operators
FundamentalOrdinarily, classes in C++ define the equality operator to test whether two objects are equivalent. That is, they usually compare every data member and treat two objects as equal if and only if all the corresponding members are equal. In line with this design philosophy, our Sales_data
equality operator should compare the bookNo
as well as the sales figures:
bool operator==(const Sales_data &lhs, const Sales_data &rhs)
{
return lhs.isbn() == rhs.isbn() &&
lhs.units_sold == rhs.units_sold &&
lhs.revenue == rhs.revenue;
}
bool operator!=(const Sales_data &lhs, const Sales_data &rhs)
{
return !(lhs == rhs);
}
The definition of these functions is trivial. More important are the design principles that these functions embody:
- If a class has an operation to determine whether two objects are equal, it should define that function as
operator==
rather than as a named function: Users will expect to be able to compare objects using==;
providing==
means they won’t need to learn and remember a new name for the operation; and it is easier to use the library containers and algorithms with classes that define the==
operator. - If a class defines
operator==
, that operator ordinarily should determine whether the given objects contain equivalent data. - Ordinarily, the equality operator should be transitive, meaning that if
a == b
andb == c
are both true, thena == c
should also be true. - If a class defines
operator==
, it should also defineoperator!=
. Users will expect that if they can use==
then they can also use!=
, and vice versa. - One of the equality or inequality operators should delegate the work to the other. That is, one of these operators should do the real work to compare objects. The other should call the one that does the real work.
TIP
Best Practices
Classes for which there is a logical meaning for equality normally should define operator==
. Classes that define ==
make it easier for users to use the class with the library algorithms.
INFO
Exercises Section 14.3.1
Exercise 14.16: Define equality and inequality operators for your StrBlob
(§ 12.1.1, p. 456), StrBlobPtr
(§ 12.1.6, p. 474), StrVec
(§ 13.5, p. 526), and String
(§ 13.5, p. 531) classes.
Exercise 14.17: Should the class you chose for exercise 7.40 from § 7.5.1 (p. 291) define the equality operators? If so, implement them. If not, explain why not.
14.3.2. Relational Operators
FundamentalClasses for which the equality operator is defined also often (but not always) have relational operators. In particular, because the associative containers and some of the algorithms use the less-than operator, it can be useful to define an operator<
.
Ordinarily the relational operators should
- Define an ordering relation that is consistent with the requirements for use as a key to an associative container (§ 11.2.2, p. 424); and
- Define a relation that is consistent with
==
if the class has both operators. In particular, if two objects are!=
, then one object should be<
the other.
Although we might think our Sales_data
class should support the relational operators, it turns out that it probably should not do so. The reasons are subtle and are worth understanding.
We might think that we’d define <
similarly to compareIsbn
(§ 11.2.2, p. 425). That function compared Sales_data
objects by comparing their ISBNs. Although compareIsbn
provides an ordering relation that meets requirment 1, that function yields results that are inconsistent with our definition of ==
. As a result, it does not meet requirement 2.
The Sales_data ==
operator treats two transactions with the same ISBN as unequal if they have different revenue
or units_sold
members. If we defined the <
operator to compare only the ISBN member, then two objects with the same ISBN but different units_sold
or revenue
would compare as unequal, but neither object would be less than the other. Ordinarily, if we have two objects, neither of which is less than the other, then we expect that those objects are equal.
We might think that we should, therefore, define operator<
to compare each data element in turn. We could define operator<
to compare objects with equal isbn
s by looking next at the units_sold
and then at the revenue
members.
However, there is nothing essential about this ordering. Depending on how we plan to use the class, we might want to define the order based first on either revenue
or units_sold
. We might want those objects with fewer units_sold
to be “less than” those with more. Or we might want to consider those with smaller revenue
“less than” those with more.
For Sales_data
, there is no single logical definition of <
. Thus, it is better for this class not to define <
at all.
TIP
Best Practices
If a single logical definition for <
exists, classes usually should define the <
operator. However, if the class also has ==
, define <
only if the definitions of <
and ==
yield consistent results.
INFO
Exercises Section 14.3.2
Exercise 14.18: Define relational operators for your StrBlob
, StrBlobPtr
, StrVec
, and String
classes.
Exercise 14.19: Should the class you chose for exercise 7.40 from § 7.5.1 (p. 291) define the relational operators? If so, implement them. If not, explain why not.