Team LiB
Previous Section Next Section

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.

 

Image 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.

 

 

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

 
Image

Ordinarily, 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 and b == c are both true, then a == c should also be true.

 

• If a class defines operator==, it should also define operator!=. 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.

 

Image 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.

 

 

Exercises Section 14.3.1

 

Exercise 14.16: Define equality and inequality operators for your StrBlob12.1.1, p. 456), StrBlobPtr12.1.6, p. 474), StrVec13.5, p. 526), and String13.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

 
Image

Classes 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

 

1. 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

 

2. 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.

 
Image

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 compareIsbn11.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 isbns 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.

 

Image 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.

 

 

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.

 

 
Team LiB
Previous Section Next Section