Section 14.1. Defining an Overloaded Operator


14.1. Defining an Overloaded Operator

Overloaded operators are functions with special names: the keyword operator followed by the symbol for the operator being defined. Like any other function, an overloaded operator has a return type and a parameter list.

      Sales_item operator+(const Sales_item&, const Sales_item&); 

declares the addition operator that can be used to "add" two Sales_item objects and yields a copy of a Sales_item object.

With the exception of the function-call operator, an overloaded operator has the same number of parameters (including the implicit this pointer for member functions) as the operator has operands. The function-call operator takes any number of operands.

Overloaded Operator Names

Table 14.1 on the next page lists the operators that may be overloaded. Those that may not be overloaded are listed in Table 14.2.

Table 14.1. Overloadable Operators

+

-

*

/

%

^

&

|

~

!

,

=

<

>

<=

>=

++

--

<<

>>

==

!=

&&

||

+=

-=

/=

%=

^=

&=

|=

*=

<<=

>>=

[]

()

->

->*

new

new []

delete

delete []


Table 14.2. Operators That Cannot Be Overloaded

::

.*

.

?:


New operators may not be created by concatenating other legal symbols. For example, it would be illegal to attempt to define an operator** to provide exponentiation. Overloading new and delete is described in Chapter 18 (p. 753).

Overloaded Operators Must Have an Operand of Class Type

The meaning of an operator for the built-in types may not be changed. For example, the built-in integer addition operation cannot be redefined:

      // error: cannot redefine built-in operator for ints      int operator+(int, int); 

Nor may additional operators be defined for the built-in data types. For example, an operator+ taking two operands of array types cannot be defined.

An overloaded operator must have at least one operand of class or enumeration (Section 2.7, p. 62) type. This rule enforces the requirement that an overloaded operator may not redefine the meaning of the operators when applied to objects of built-in type.



Precedence and Associativity Are Fixed

The precedence (Section 5.10.1, p. 168), associativity, or number of operands of an operator cannot be changed. Regardless of the type of the operands and regardless of the definition of what the operations do, this expression

      x == y +z; 

always binds the arguments y and z to operator+ and uses that result as the right-hand operand to operator==.

Four symbols (+, -, *, and &) serve as both unary and binary operators. Either or both of these operators can be overloaded. Which operator is being defined is controlled by the number of operands. Default arguments for overloaded operators are illegal, except for operator(), the function-call operator.

Short-Ciruit Evaluation Is Not Preserved

Overloaded operators make no guarantees about the order in which operands are evaluated. In particular, the operand-evaluation guarantees of the built-in logical AND, logical OR (Section 5.2, p. 152), and comma (Section 5.9, p. 168) operators are not preserved. Both operands to an overloaded version of && or || are always evaluated. The order in which those operands are evaluated is not stipulated. The order in which the operands to the comma are evaluated is also not defined. For this reason, it is usually a bad idea to overload &&, ||, or the comma operator.

Class Member versus Nonmember

Most overloaded operators may be defined as ordinary nonmember functions or as class member functions.

Overloaded functions that are members of a class may appear to have one less parameter than the number of operands. Operators that are member functions have an implicit this parameter that is bound to the first operand.



An overloaded unary operator has no (explicit) parameter if it is a member function and one parameter if it is a nonmember function. Similarly, an overloaded binary operator would have one parameter when defined as a member and two parameters when defined as a nonmember function.

The Sales_item class offers a good example of member and nonmember binary operators. We know that the class has an addition operator. Because it has an addition operator, we ought to define a compound-assignment (+=) operator as well. This operator will add the value of one Sales_item object into another.

Ordinarily we define the arithmetic and relational operators as nonmember functions and we define assignment operators as members:

      // member binary operator: left-hand operand bound to implicit this pointer      Sales_item& Sales_item::operator+=(const Sales_item&);      // nonmember binary operator: must declare a parameter for each operand      Sales_item operator+(const Sales_item&, const Sales_item&); 

Both addition and compound assignment are binary operators, yet these functions define a different number of parameters. The reason for the discrepancy is the this pointer.

When an operator is a member function, this points to the left-hand operand. Thus, the nonmember operator+ defines two parameters, both references to const Sales_item objects. Even though compound assignment is a binary operator, the member compound-assignment operator takes only one (explicit) parameter. When the operator is used, a pointer to the left-hand operand is automatically bound to this and the right-hand operand is bound to the function's sole parameter.

It is also worth noting that compound assignment returns a reference and the addition operator returns a Sales_item object. This difference matches the return types of these operators when applied to arithmetic types: Addition yields an rvalue and compound assignment returns a reference to the left-hand operand.

Operator Overloading and Friendship

When operators are defined as nonmember functions, they often must be made friends (Section 12.5, p. 465) of the class(es) on which they operate. We'll see later in this chapter two reasons why operators might be defined as nonmembers. In such cases, the operator often needs access to the private parts of the class.

Our Sales_item class is again a good example of why some operators need to be friends. It defines one member operator and has three nonmember operators. Those nonmember operators, which need access to the private data members, are declared as friends:

      class Sales_item {          friend std::istream& operator>>                        (std::istream&, Sales_item&);          friend std::ostream& operator<<                        (std::ostream&, const Sales_item&);      public:          Sales_item& operator+=(const Sales_item&);      };          Sales_item operator+(const Sales_item&, const Sales_item&); 

That the input and output operators need access to the private data should not be surprising. After all, they read and write those members. On the other hand, there is no need to make the addition operator a friend. It can be implemented using the public member operator+=.

Using Overloaded Operators

We can use an overloaded operator in the same way that we'd use the operator on operands of built-in type. Assuming item1 and item2 are Sales_item objects, we might print their sum in the same way that we'd print the sum of two ints:

      cout << item1 + item2 << endl; 

This expression implicitly calls the operator+ that we defined for Sales_items.

We also can call an overloaded operator function in the same way that we call an ordinary function: We name the function and pass an appropriate number of arguments of the appropriate type:

      // equivalent direct call to nonmember operator function      cout << operator+(item1, item2) << endl; 

This call has the same effect as the expression that added item1 and item2.

We call a member operator function the same way we call any other member function: We name an object on which to run the function and then use the dot or arrow operator to fetch the function we wish to call passing the required number and type of arguments. In the case of a binary member operator function, we must pass a single operand:

      item1 += item2;            // expression based "call"      item1.operator+=(item2);   // equivalent call to member operator function 

Each of these statements adds the value of item2 into item1. In the first case, we implicitly call the overloaded operator function using expression syntax. In the second, we call the member operator function on the object item1.

Exercises Section 14.1

Exercise 14.1:

In what ways does an overloaded operator differ from a built-in operator? In what ways are overloaded operators the same as the built-in operators?

Exercise 14.2:

Write declarations for the overloaded input, output, addition and compound-assignment operators for Sales_item.

Exercise 14.3:

Explain the following program, assuming that the Sales_item constructor that takes a string is not explicit. Explain what happens if that constructor is explicit.

      string null_book = "9-999-99999-9";      Sales_item item(cin);      item += null_book; 

Exercise 14.4:

Both the string and vector types define an overloaded == that can be used to compare objects of those types. Identify which version of == is applied in each of the following expressions:

      string s; vector<string> svec1, svec2;      "cobble" == "stone"      svec1[0] == svec2[0];      svec1 == svec2 


14.1.1. Overloaded Operator Design

When designing a class there are some useful rules of thumb to keep in mind when deciding which, if any, overloaded operators to provide.

Don't Overload Operators with Built-in Meanings

The assignment, address of, and comma operators have default meanings for operands of class types. If there is no overloaded version specified, the compiler defines its own version of these operators:

  • The synthesized assignment operator (Section 13.2, p. 482) does memberwise assignment: It uses each member's own assignment operator to assign each member in turn.

  • By default the address of (&) and comma (,) operators execute on class type objects the same way they do on objects of built-in type. The address of operator returns the address in memory of the object to which it is applied. The comma operator evaluates each expression from left to right and returns the value of its rightmost operand.

  • The built-in logical AND (&&) and OR(||) operators apply short-circuit evaluation (Section 5.2, p. 152). If the operator is redefined, the short-circuit nature of the operators is lost.

The meaning of these operators can be changed by redefining them for operands of a given class type.

It is usually not a good idea to overload the comma, address-of, logical AND, or logical OR operators. These operators have built-in meanings that are useful and become inaccessible if we define our own versions.



We sometimes must define our own version of assignment. When we do so, it should behave analogously to the synthesized operators: After an assignment, the values in the left-hand and right-hand operands should be the same and the operator should return a reference to its left-hand operand. Overloaded assignment should customize the built-in meaning of assignment, not circumvent it.

Most Operators Have No Meaning for Class Objects

Operators other than assignment, address-of, and comma have no meaning when applied to an operand of class type unless an overloaded definition is provided. When designing a class, we decide which, if any, operators to support.

The best way to design operators for a class is first to design the class' public interface. Once the interface is defined, it is possible to think about which operations should be defined as overloaded operators. Those operations with a logical mapping to an operator are good candidates. For example,

  • An operation to test for equality should use operator==.

  • Input and output are normally done by overloading the shift operators.

  • An operation to test whether the object is empty could be represented by the logical NOT operator, operator!.

Compound Assignment Operators

If a class has an arithmetic (Section 5.1, p. 149) or bitwise (Section 5.3, p. 154) operator, then it is usually a good idea to provide the corresponding compound-assignment operator as well. For example, our Sales_item class defined the + operator. Logically, it also should define +=. Needless to say, the += operator should be defined to behave the same way the built-in operators do: Compound assignment should behave as + followed by =.

Equality and Relational Operators

Classes that will be used as the key type of an associative container should define the < operator. The associative containers by default use the < operator of the key type. Even if the type will be stored only in a sequential container, the class ordinarily should define the equality (==) and less-than (<) operators. The reason is that many algorithms assume that these operators exist. As an example, the sort algorithm uses < and find uses ==.

Caution: Use Operator Overloading Judiciously

Each operator has an associated meaning from its use on the built-in types. Binary +, for example, is strongly identified with addition. Mapping binary + to an analogous operation for a class type can provide a convenient notational shorthand. For example, the library string type, following a convention common to many programming languages, uses + to represent concatenation"adding" one string to the other.

Operator overloading is most useful when there is a logical mapping of a built-in operator to an operation on our type. Using overloaded operators rather than inventing named operations can make our programs more natural and intuitive. Overuse or outright abuse of operator overloading can make our classes incomprehensible.

Obvious abuses of operator overloading rarely happen in practice. As an example, no responsible programmer would define operator+ to perform subtraction. More common, but still inadvisable, are uses that contort an operator's "normal" meaning to force a fit to a given type. Operators should be used only for operations that are likely to be unambiguous to users. An operator with ambiguous meaning, in this sense, is one that supports equally well a number of different interpretations.

When the meaning of an overloaded operator is not obvious, it is better to give the operation a name. It is also usually better to use a named function rather than an operator for operations that are rarely done. If the operation is unusual, the brevity of using an operator is unnecessary.




If the class defines the equality operator, it should also define !=. Users of the class will assume that if they can compare for equality, they can also compare for inequality. The same argument applies to the other relational operators as well. If the class defines <, then it probably should define all four relational operators (>, >=, <, and <=).

Choosing Member or Nonmember Implementation

When designing the overloaded operators for a class, we must choose whether to make each operator a class member or an ordinary nonmember function. In some cases, the programmer has no choice; the operator must be a member. In other cases, there are some rules of thumb that can help guide the decision. The following guidelines can be of help when deciding whether to make an operator a member or an ordinary nonmember function:

  • The assignment (=), subscript ([]), call (()), and member access arrow (->) operators must be defined as members. Defining any of these operators as a nonmember function is flagged at compile time as an error.

  • Like assignment, the compound-assignment operators ordinarily ought to be members of the class. Unlike assignment, they are not required to be so and the compiler will not complain if a nonmember compound-assignment operator is defined.

  • Other operators that change the state of their object or that are closely tied to their given typesuch as increment, decrement, and dereferenceusually should be members of the class.

  • Symmetric operators, such as the arithmetic, equality, relational, and bitwise operators, are best defined as ordinary nonmember functions.

Exercises Section 14.1.1

Exercise 14.5:

List the operators that must be members of a class.

Exercise 14.6:

Explain why and whether each of the following operators should be class members:

      (a) + (b) += (c) ++ (d) -> (e) << (f) && (g) == (h) () 




C++ Primer
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2006
Pages: 223
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net