If we need type conversions on all parameters to a function including the one pointed to by the this pointer, the function must be a non-member.
Having classes support implicit type conversions is generally a bad idea. One good and common exception to the rule is when creating numerical types, for example, we want to allow implicit conversions from integers to user-defined rationals type:
|
|
We’d like to support arithmetic operation of multiplication, and one way is to declare it as a member function of Rational:
|
|
This design is fine to multiply rationals with rationals:
|
|
However, for mixed-mode operations, where Rationals is multiplied with ints, there will be a potential error:
|
|
This problem is clearer for analysis when we rewrite the last two examples in their equivalent functional form:
|
|
For the first statement:
the object oneHalf is an instance of a class that contains an operator* taking a Rational as its argument. Compilers know we’re passing an int and that the function requires a Rational, and they also know they can conjure up a suitable Rational by implicit type conversion - calling the Rational constructor with the int we provided, so compilers will happily call that function as if it had been written like this:
|
|
Of course, compilers are allowed to do this implicit type conversion only because a non-explicit constructor is involved. If we add keyword explicit before the constructor above, neither of the two mixed-type multiplication statements would compile.
Now for the second statement:
it turns out that parameters are eligible for implicit type conversion only if they are listed in the parameter list. The implicit parameter pointed to by this, which is also the obejct on which the member function is invoked, is never eligible for implicit conversions. Back to the second statement, int type 2 does not have associated class containing a function operator* taking a Rational type object as its argument, nor is 2 listed in the parameter list for an implicit type conversion to Rational. That is the cause of compilation failure.
In fact, when compilers fail to find a matching member function, they will also look for non-member operator*s (i.e., ones at namespace or global scope) that can be called like this:
|
|
And this is exactly what we want if we’d like to support mixed-mode arithmetic: make opeartor* a non-member function, thus allowing compilers to perform implicit type conversions on all arguments:
|
|
Now comes anoter worry: should operator* be made a friend of the Rational class?
In this case, the answer is no, because operator* can be implemented entirely through Rational’s public interface. This leads to an important observation:
The opposite of a member function is a non-member function, not a frient function.
There’s some misunderstanding that if a function is related to a class and should not be a member (due, for example, to type conversions on all arguments), it should be a friend. This reasoning turns out to be flawed. The basic rule is to avoid friend functions whenever we can.
P.S.: This item contains the truth, but it is not the whole truth. When we cross the line from Object-Oriented C++ into Template C++ (item 1), and make
Rationala class template instead of a class, refer to item 46 for some new issues to consider, new ways to solve them, and new design implications.