Effective C ++ 2e Item19

zhaozj2021-02-11  238

Terms 19: Distinguishing member functions, non-member functions and friend functions

The biggest difference between member functions and non-member functions is that the member function can be a virtual rather than a member function. So, if a function must perform a dynamic binding (see Terms 38), you must use a virtual function, and the virtual function must be a member function of a class. This is so simple about this. If the function does not have to be virtual, the situation is slightly complicated.

Look at the rational number of rational numbers below:

Class Rational {public: Rational (int name = 0, int devenator = 1); int namerator () const; int devenator () const;

PRIVATE: ...};

This is a class that is not used. (Use the terminology of the Terms 18, the interface is indeed, but it is not complete enough.) So, to increase the addition, minus, multiplied arithmetic operation support, however, the member function is still non-member function, or non-member The friend function is implemented?

When you don't get your mind, use the object-oriented way! The rumor multiplication is to contact the Rational class, so write a member function to the class.

Class russ {public:

...

Const Rational Operator * (Const Rational & Rhs) Const;

(If you don't understand why this function is declared in this way - returns a const value and takes a reference reference as its parameters - Reference Terms 21-23.)

It is now easy to multiply the rational number:

Rational Oneeighth (1, 8); Rational Onehalf (1, 2);

Rational Result = Onehalf * Oneeighth; // Good operation

Result = result * oneeighth; // Good operation

But don't satisfy, you have to support mixed types, such as Rational, and int. But when writing the following code, only half work:

Result = onehalf * 2; // is working well

Result = 2 * onehalf; // error!

This is a bad body. remember? Multiplication must meet the exchange law.

If you rewrite the above examples in the form of the equivalent function, the reason is obvious:

Result = onehalf.operator * (2); // Good operation

Result = 2.operator * (onehalf); // error!

Object OneHalf is an instance of a class containing the Operator * function, so the compiler calls that function. The integer 2 does not have a corresponding class, so there is no Operator * member function. The compiler also searches for a non-member Operator * function that can be called below (ie, Operator * functions in a visible namespace or global Operator * function):

Result = Operator * (2, onehalf); // Error!

But there is no such parameter for the non-member Operator * function of INT and Rational, so the search failed.

Let's take a look at the successful call. Its second parameter is integer 2, but Rational :: Operator * The desired parameter is the Rational object. what happened? Why can't I work in one place and another place? The secret is implicit type conversion. The value of the compiler knows that the function is required to function, but it also knows that the Rational's constructor is converted into a suitable Rational Rational, so there is a successful call (see Terms M19). In other words, the compiler is in the case where this call is like this:

Const Rational Temp (2); // Generate a temporary // Rational object from 2

Result = onehalf * temp; // with onehalf.operator * (temp);

Of course, only if the constructor involved is not declared as ExPlicit, this is because the Explicit constructor cannot be used for implicit conversion, which is explicit meaning. If the Rational image is defined below:

Class Rational {public: explicit runch = 0, // This constructor is int Denominator = 1); // expedition ...

Const Rational Operator * (Const Rational & Rhs) Const;

...

}

So, the following statements cannot be compiled:

Result = onehalf * 2; // error! Result = 2 * Onehalf; // Error!

This will not provide support for mixed operations, but at least two statements are consistent.

However, this class we just study is to design implicit conversions that can allow fixed types to Rational - this is why Rational constructor does not declare the reason for explicit. In this way, the compiler will perform the necessary implicit conversion to make the first assignment statement of the above RESULT by compile. In fact, if needed, the compiler performs this implicit type conversion for each parameter of each function. However, it only converts the parameters listed in the function parameter table, which will never be converted to the objects where the member function is located (ie, the object corresponding to the * THIS pointer in the member function). That's why this statement is working:

Result = onehalf.operator * (2); // Converts Int -> Rational

And this statement is not:

Result = 2.operator * (onehalf); // Will not convert // int-> xi

The first case is operated by a parameter column in the function declaration, and the second case is not.

Despite this, you may still want to support hybrid arithmetic operations, and the implementation of the implementation should now be clear: make Operator * becomes a non-member function, allowing the compiler to perform implicit type conversions for all parameters:

Class russ {

... // Contains No Operator *

}

// In the global or a namespace declaration, // See the Terms M20 Learn Why do you want to do this Const Rational Operator * (Const Rational & LHS, Const Rational & Return Rational (lhs.Numerator () * rhs.Numerator (), LHS. Denominator () * rhs.denominator ());} Rational OnefouRTH (1, 4); Rational Result;

Result = onefouRTh * 2; // Working good results = 2 * OnefouRTH; // Long live, it also works!

This is of course a perfect ending, but there is also a worry: Operator * should be a friend of the Rational class?

In this case, the answer is unnecessary. Because Operator * can be implemented completely through the publication of the public. The code above is doing this. As long as you can avoid using a friend function, it is avoided, because it is almost in real life, the trouble brought by Friends (friends) is much more helpful than it (he / her).

However, in many cases, it is not a member's function that may also be part of the class interface, and they need to access the non-public members of the class.

Let us look back and look at the main example of this book, String class. If you want to override Operator >> and Operator << to read and write String objects, you will quickly find that they can't be a member function. If it is a member function, you must put the String object on the left of them when calling them:

// An incorrect Operator >> and // Operator << as a class class string {public: string (const char * value);

...

iStream & Operator >> (ISTREAM & OPERATOR << (Ostream & Output);

PRIVATE: CHAR * DATA;

String S;

S >> cin; // legal, but // has violations

s << cout; //

This will confuse others. So these functions cannot be a member function. Note this situation and the previous difference. The goal here is natural call syntax, the front concern is implicit type conversion.

So, if you design these functions, just like this:

iStream & Operator >> (ISTREAM & INPUT, STRING & STRING) {delete [] string.data;

Read from Input Into Some Memory, And Make String.Data Point To IT

Return INPUT;

Ostream & Operator << (Ostream & Output, Const string & string) {Return Output << String.Data;

Note that the above two functions must access the String class Data member, and this member is private. But we already know that this function must be a non-member function. This way, there is no choice: non-member functions that need to access non-public members can only be a class of friend functions. The conclusions given this article are as follows. Suppose F is a function that wants to declare correctly, c is a class related to it:

· The virtual function must be a member function. If f must be a virtual function, let it become a member function of C.

· Operator >> and Operator << Never be a member function. If F is Operator >> or Operator <<, let F become non-member functions. If f also needs to access the C's non-public member, let F become the C 's favorite.

· Only non-member functions conversion to the leftmost parameters. If f requires type conversion to the leftmost parameters, let F become non-member functions. If f also needs to access the C's non-public member, let F become the C 's favorite.

· Statement as a member function in other cases. If the above situation is not, f becomes a member function of C.

转载请注明原文地址:https://www.9cbs.com/read-4468.html

New Post(0)