[Translation] Negative tive C ++, 3rd edition, item 24: When you want to apply type conversions (type conversion) to all parameters (parameters), declare it as non-

Source: Internet
Author: User

Item 24: When you want to apply type conversions (type conversion) to all parameters (parameters), declare it as non-member functions (non-member function)

By Scott Meyers

Translator: fatalerror99 (itepub's nirvana)

Release: http://blog.csdn.net/fatalerror99/

In the book's introduction (Introduction), it is often a bad idea to allow classes to support implicit type conversions (implicit type conversion. Of course, this rule has an exception. The most common one is when creating a value type. For example, if you design a class that represents rational numbers (rational number), it seems unreasonable to allow implicit conversions (implicit conversion) from integers (integer) to rational number (rational number. This is indeed no better than C ++'s built-in conversion from int to double (built-in conversion) more unreasonable (and more reasonable than C ++'s built-in conversion (built-in conversion) from double to int ). In this case, you can start your rational class in this way:

Class rational {
Public:
Rational (INT Numerator = 0, // Ctor is deliberately not explicit;
Int Denominator = 1); // allows implicit int-to-rational
// Conversions

Int numerator () const; // accessors for numerator and
Int denominator () const; // denominator-see item 22

PRIVATE:
...
};

You know that you should support arithmetic operations such as addition and multiplication, but you are not sure to use member functions (member function), non-member functions (non-member function ), or non-member functions that are friends (non-member friend functions) to implement them. Your intuition tells you that you should stick to the object-oriented principle when you are not sure. When you understand this, it is concluded that because the multiplication of rational numbers is related to rational classes, it seems natural to implement rational operators * in rational classes. However, unlike intuition, item 23 points out that the idea of placing functions within the class they are associated with is sometimes the opposite of the object-oriented principle, however, let's put it aside to study how to make operator * a rational member function (member function:

Class rational {
Public:
...

Const rational operator * (const rational & RHs) const;
};

(If you are not sure why this function is declared as it now -- returns the result of a const by-value, but get a reference-to-const as its parameter-see items and 21 .)

This design makes it easy to multiply rational numbers:

Rational oneeighth (1, 8 );
Rational onehalf (1, 2 );

Rational result = onehalf * oneeighth; // fine

Result = Result * oneeighth; // fine

But you are not satisfied. You also want to support the mixed-mode operation so that rationals can be multiplied by other types (such as ints. After all, few things are as normal as two numbers are multiplied, even if they happen to be different types of numbers.

However, when you try to perform arithmetic operations in the mixed mode, you find that it only takes half of the time to work:

Result = onehalf * 2; // fine

Result = 2 * onehalf; // error!

This is a bad sign. Multiplication must be interchangeable. Remember?

When you rewrite the last two examples into another form of equivalent functionality, the source of the problem becomes obvious:

Result = onehalf. Operator * (2); // fine

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

Object onehalf is an instance of class containing operator *, so the compiler calls that function. However, integer 2 has nothing to do with the class, so there is no operator * member function (member function ). The compiler also looks for the operator * s (that is, within the namespace or global scope) of the non-member (non-member) that can be called as follows ):

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

However, in this example, no non-member (non-member) obtains an int and a rational operator *, so the search fails.

Let's take a look at the successful call. You will find that its second parameter is Integer 2, but rational: Operator * obtains a rational object as its real parameter. What happened here? Why does 2 work in one location, but not in other places?

Implicit type conversion (implicit type conversion) occurs ). The compiler knows that you pass an int and that function requires a rational one, but they also know that they can use the int you provide to call rational Constructor (constructor) and they can make a rational match, this is what they do. In other words, they look at that call more or less like this:

Const rational temp (2); // create a temporary
// Rational object from 2

Result = onehalf * temp; // same as onehalf. Operator * (temp );

Of course, the compiler does this only because it provides a non-explicit Constructor (non-explicit constructor ). If rational Constructor (constructor) is explicit, these statements cannot be compiled:

Result = onehalf * 2; // error! (With explicit ctor );
// Can't convert 2 to rational

Result = 2 * onehalf; // same error, same problem

An error occurred while supporting the mixed mode operation, but the behavior of at least two statements will be consistent.

However, your goal is to maintain consistency and support mixed mode operations. That is to say, a design that enables compilation of both statements above. Let's return the two statements to see why, even if the rational Constructor (constructor) is not explicit, it can be compiled but the other cannot:

Result = onehalf * 2; // fine (with non-explicit ctor)

Result = 2 * onehalf; // error! (Even with non-explicit ctor)

The reason is that they are eligible for implicit type conversion (implicit type conversion) only when parameters (parameter) is listed in the parameter list (parameter list ). The implicit parameter (implicit parameter) of the object called by the member function-the one referred to by this-is not eligible for implicit conversions (implicit conversion ). This is why the first call can be compiled but the second call cannot. In the first case, a parameter is listed in the parameter list, but not in the second case.

However, you still want to support mixed-mode operations. The method to do this may be clear: let operator * become a non-member function (non-member function ), this allows the compiler to apply implicit type conversions (implicit type conversion)AllArguments (all arguments ):

Class rational {

... // Contains no operator *
};
Const rational operator * (const rational & LHS,// Now a non-member
Const rational & RHs)// Function
{
Return rational (LHS. numerator () * RHS. numerator (),
LHS. denominator () * RHS. denominator ());
}
Rational Onefourth (1, 4 );
Rational result;

Result = Onefourth * 2; // fine
Result = 2 * Onefourth; // Hooray, it works!

This indeed makes the story a perfect ending, but there is a flaw in it. Shouldn't operator * be the rational class's friend?

In this case, the answer is no, because it can be fully implemented based on the rational public interface (public interface) operator. The above code shows a way to do this. This leads to an important conclusion: Compared with a member function (member function ),Non-memberFunction (non-member function), rather than a friend function ). Too many c ++ Programmers think that if a function is related to a class and should not be a member (member) (for example, because all arguments (real parameters) both require type conversions (type conversion), which should be a friend ). This example proves that such reasoning is flawed. At any time, you can only avoid friend functions (friend functions), because, in real life, friends usually have more trouble than their value. Of course, sometimes friendship is necessary, but it turns out that just because the function should not be a member (member) doesn't automatically mean it should be a friend ).

This item contains truth and only truth, but it is not complete truth. When you go from object-oriented C ++ to template C ++ (see item 1) and make rational into a classTemplate(Class template) instead of a class, there are new problems that need to be considered, there are new ways to solve them, and some surprising Design Associations. The solution and relevance are the subject of item 46.

Things to remember

  • If you need to use type conversions (type conversion) on all parameters (parameters) of a function (including those referred to by this ), this function must be a non-member (non-member ).

 

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.