Conceptually, 2.75 * B should be the same as B * 2.75, but the first expression cannot correspond to a member function because 2.75 is not a type Time object. Remember, the left operand is the invoking object, but 2.75 is not an object. So the compiler cannot replace the expression with a member function call.
One way around this difficulty is to tell everyone (and to remember yourself) that you can only write B * 2.75 but never write 2.75 * B. This is a server-friendly, client-beware solution, and that’s not what OOP is about.
However, there is another possibility—using a nonmember function. (Remember, most operators can be overloaded using either member or nonmember functions.) A nonmember function is not invoked by an object; instead, any values it uses, including objects, are explicit arguments. Thus, the compiler could match the expression
A = 2.75 * B; // cannot correspond to a member function
to the following nonmember function call:
A = operator*(2.75, B);
The function would have this prototype:
Time operator*(double m, const Time & t);
With the nonmember overloaded operator function, the left operand of an operator expression corresponds to the first argument of the operator function, and the right operand corresponds to the second argument. Meanwhile, the original member function handles operands in the opposite order—that is, a Time value multiplied by a double value.
Using a nonmember function solves the problem of getting the operands in the desired order (first double and then Time), but it raises a new problem: Nonmember functions can’t directly access private data in a class. Well, at least ordinary nonmember functions lack access. But there is a special category of nonmember functions, called
Creating Friends
The first step toward creating a friend function is to place a prototype in the class declaration and prefix the declaration with the keyword friend:
friend Time operator*(double m, const Time & t); // goes in class declaration
This prototype has two implications:
• Although the operator*() function is declared in the class declaration, it is not a member function. So it isn’t invoked by using the membership operator.
• Although the operator*() function is not a member function, it has the same access rights as a member function.
The second step is to write the function definition. Because it is not a member function, you don’t use the Time:: qualifier. Also you don’t use the friend keyword in the definition. The definition should look like this:
Time operator*(double m, const Time & t) // friend not used in definition
{
Time result;
long totalminutes = t.hours * mult * 60 +t. minutes * mult;
result.hours = totalminutes / 60;
result.minutes = totalminutes % 60;
return result;
}
With this declaration and definition, the statement
A = 2.75 * B;
translates to the following and invokes the nonmember friend function just defined:
A = operator*(2.75, B);
In short, a friend function to a class is a nonmember function that has the same access rights as a member function.
Are Friends Unfaithful to OOP?
At first glance, it might seem that friends violate the OOP principle of data hiding because the friend mechanism allows nonmember functions to access private data. However, that’s an overly narrow view. Instead, you should think of friend functions as part of an extended interface for a class. For example, from a conceptual point of view, multiplying a double by a Time value is pretty much the same as multiplying a Time value by a double. That the first requires a friend function whereas the second can be done with a member function is the result of C++ syntax, not of a deep conceptual difference. By using both a friend function and a class method, you can express either operation with the same user interface. Also keep in mind that only a class declaration can decide which functions are friends, so the class declaration still controls which functions access private data. In short, class methods and friends are simply two different mechanisms for expressing a class interface.
Actually, you can write this particular friend function as a non-friend by altering the definition so that it switches which value comes first in the multiplication:
Time operator*(double m, const Time & t)
{
return t * m; // use t.operator*(m)
}
The original version accessed t.minutes and t.hours explicitly, so it had to be a friend. This version only uses the Time object t as a whole, letting a member function handle the private values, so this version doesn’t have to be a friend. Nonetheless, there are reasons to make this version a friend, too. Most importantly, it ties the function in as part of the official class interface. Second, if you later find a need for the function to access private data directly, you only have to change the function definition and not the class prototype.
Tip