Reading Notes Objective c ++ Item 24 if all parameters of a function require type conversion, declare the function as a non-member function. objective is a non-member function.

1. If you declare a function that requires implicit type conversion as a member function, a problem may occur.

It is a bad idea to make classes support implicit conversion. Of course, there are exceptions. The most common example is the numerical type. For example, if you design a class that represents rational numbers, it is reasonable to allow implicit conversion from an integer to rational number. In the C ++ built-in type, converting from int to double is more reasonable (more reasonable than converting from double to int ). See the following example:

1 class Rational { 2 3 public: 4 5 Rational(int numerator = 0, // ctor is deliberately not explicit; 6 7 int denominator = 1); // allows implicit int-to-Rational 8 9 // conversions10 11 int numerator() const; // accessors for numerator and12 13 int denominator() const; // denominator — see Item 2214 15 private:16 17 ...18 19 };

You want to support arithmetic operations of rational numbers, such as addition and multiplication, but you do not know whether it is implemented through membership functions, non-member functions, or non-member membership functions. Your intuition tells you that when you are hesitant, you should use object-oriented features. The product of rational number is related to the rational number class. It is natural for all operator * implementations of rational number to be placed in the Rationl class. However, the intuition is that Item 23 has demonstrated that putting a function in a class sometimes violates the object-oriented law. Now we put it aside, the following describes how to implement operator * as a member function:

1 class Rational {2 3 public:4 5 ...6 7 const Rational operator*(const Rational& rhs) const;8 9 };

(If you do not understand why the function is declared as above -- return a const value with the parameter const reference, refer to Item 3, Item 20, and Item21)

This design allows you to conveniently execute the multiplication of rational numbers:

1 Rational oneEighth(1, 8);2 3 Rational oneHalf(1, 2);4 5 Rational result = oneHalf * oneEighth; // fine6 7 result = result * oneEighth; // fine

But you are not satisfied. You want to support operations in the mixed mode. For example, you can support multiplication between the int type and the Rational type. Multiplication between different types is also natural.

When you try this mixed mode operation, you will find that only half of the operation is correct:

1 result = oneHalf * 2; // fine2 3 result = 2 * oneHalf; // error!

This is not good. Multiplication supports the exchange law.

2. where is the problem?

Write the above example in the form of an equivalent function, and you will know where the problem is:

1 result = oneHalf.operator*(2); // fine2 3 result = 2.operator*(oneHalf ); // error!

The oneHalf object is an instance of the Rational class, and the Rational supports operator * operations, so the compiler can call this function. However, integer 2 has no associated class, so there is no operator * member function. The compiler will also look for non-member operator * functions (that is, namespaces or functions within the global scope ):

1 result = operator*(2, oneHalf ); // error!

However, in this example, there is no non-member function with int and Rational type parameters, so the search will fail.

Let's take a look at the successfully called function. You will find that the second parameter is Integer 2, but Rational: operator * uses the Rational object as the parameter. What happened here? Why are they both 2? One can be the other, but not the other?

Yes, implicit type conversion occurs here. The compiler knows that the function requires the Rational type, but you pass the real parameters of the int type. They also know that by calling the Rational constructor, you can convert the int arguments you provided into a Rational arguments, which is what the compiler does. They are called as follows:

1 const Rational temp(2); // create a temporary2 3 // Rational object from 24 5 result = oneHalf * temp; // same as oneHalf.operator*(temp);

Of course, the compiler can do this only because the class provides a non-explicit constructor. If the Rational class constructor is explicit, the following two sentences will encounter errors:

1 result = oneHalf * 2; // error! (with explicit ctor);2 3 // can’t convert 2 to Rational4 5 result = 2 * oneHalf; // same error, same problem

In this way, the operation in the mixed mode is not supported, but the behavior of at least two sentences is now consistent.

However, your goal is to support both mixed-mode operations and consistency, that is, you need a design so that the above two sentences can be compiled. Back to the example above, when the Rational constructor is non-explicit, Why can't one be compiled by another?

It looks like this. Only parameters in the parameter list are eligible for implicit type conversion. The implicit parameter that calls the member function -- the one pointed to by this pointer -- is not eligible for implicit type conversion. This is why the first call is successful and the second call fails.

3. What is the solution?

However, you still want to support mixed-mode arithmetic operation, but the method may be clear now: Make operator * A non-member function, in this way, the compiler is allowed to perform implicit type conversion on all parameters:

1 class Rational { 2 3 ... // contains no operator* 4 5 }; 6 7 const Rational operator*(const Rational& lhs, // now a non-member 8 9 const Rational& rhs) // function10 11 {12 13 return Rational(lhs.numerator() * rhs.numerator(),14 15 lhs.denominator() * rhs.denominator());16 17 }18 19 Rational oneFourth(1, 4);20 21 Rational result;22 23 result = oneFourth * 2; // fine24 25 result = 2 * oneFourth; // hooray, it works!

4. Should Operator * be implemented as a friend function?

The story has a perfect ending, but there is a lingering worry. Should Operator * be implemented as a friend of the Rational class?

In this case, the answer is No. Because operator * can be implemented entirely by using the Rational public interface. The above code is an implementation method. We can draw an important conclusion:**The antonym of a member function is a non-member function rather than a friend function.**. Many c ++ Programmers think that if a function in a class is not a member function (for example, type conversion is required for all parameters), then it should be a friend function. The above example shows that such reasoning is flawed. Try to avoid using friend functions. Just like in the examples of life, friends may cause more trouble than getting help from them.

5. Other problems

If you convert from object-oriented C ++ to template C ++ and implement Rational into a class template, there will be new problems to be considered and there will be new ways to solve them. For these questions, methods and designs, refer to Item 46.