Effective C ++ 2e Item23

zhaozj2021-02-11  234

Terms 23: Do not try to return a reference when you return an object

It is said that Einstein has proposed such suggestions: making things simple as much as possible, but don't be too simple. Similar statements in the C language should be: Efficient as much as possible, but don't be too efficient.

Once the programmer seizes the "pass value" in efficiency handle (see Terms 22), they will become very extreme, and they must not dig out each of the pilot values ​​hidden in the program. I don't know, in the process of unremittingly pursuing pure "biography", they will inevitably make another serious error: pass a reference to the object that does not exist. This is not a good thing.

Look at the class that represents a rigor, which contains a friend function, used for two rational numbers:

Class Rational {public: Rational (int name = 0, int devenoman = 1);

...

PRIVATE: INT N, D; // Molecule and Division

Friend Const Rational // See Terms 21: Why Operator * (Const Rational & LHS, // Return Value is Const Const Rational & RHS)}

Inline Const Rational Operator * (Const Rational & lhs) {Return Rational (lhs.n * rhs.n, lhs.d * rhs.d);}

Obviously, this version of Operator * is the result of returning to the object through the value. If you don't consider the overhead of the object construct and destructure, you are evade the responsibility as a programmer. Another obvious fact is that unless it is necessary, no one is willing to bear the overhead of such a temporary object. So, the problem is attributed to: Is it necessary?

The answer is that if you can return a reference, there is of course no necessary. But keep in mind that reference is just a name, a name of some other object already exists. Whenever I see a reference statement, I will ask myself immediately: What is the other name? Because it must have another name (see Terms M1). Take Operator * If the function is to return a reference, it must be a reference to some other Rational object, which contains two objects multiplied.

However, it is expected that there is no reason to have such an object before calling Operator *. That is, if there is a code below:

Rational A (1, 2); // a = 1/2 granal B (3, 5); // b = 3/5 granal c = a * b; // c is 3/10

It is expected that there is an unrealistic number that has a value of 3/10. If Operator * must return a reference to such a number, you must create this number of objects yourself.

A function can only have two ways to create a new object: in the stack or on a heap. When you create an object in the stack, with a definition of a local variable, use this method, you have to write Operator *:

// The first error method of this function is Inline Const Rational & Operator * (Const Rational & LHS, Const Rational & Rhs) {Rational Result (lhs.n * rhs.n, lhs.d * rhs.d); Return Result;} This method should be vetoed because our goal is to avoid constructor being called, but Result must be constructed as other objects. In addition, this function has another more serious problem, it returns a reference to a local object, and the terms 31 have been discussed in depth.

So, create an object on the pile and return it to its reference? The heap-based object is generated by using new, so we should write Operator *:

// Second Error Method of this function inline const rulingal & operator * (Const Rational & LHS, Const Rational & RHS) {Rational * Result = new russional (lhs.n * rhs.n, lhs.d * rhs.d); Return * result;}

First, you still have to afford the overhead of the constructor call, because the memory allocated by the NEW is initialized by calling an appropriate constructor (see clauses 5 and M8). In addition, there is a problem: Who will be responsible for delete to delete the NEW generated object?

In fact, this is definitely a memory leak. Even if you can convince the caller to get the function return value address, then use Delete to delete it (absolutely impossible - Terms 31 show what this code will be like this), but some complex expressions will occur The temporary value of the name, the programmer is impossible. E.g:

Rational W, X, Y, Z;

w = x * y * z;

Both the call to Operator * produce a temporary value without names, and the programmer can't see it, so I can't delete it. (See Terms for Refer to Terms 31)

Perhaps you will miss you more than a general bear - or a general programmer - to be smart; maybe, you noticed that the method of creating objects on the stack and pile avoids calls to the constructor; maybe, you think of us The initial goal is to avoid this call for constructor; maybe, you have a way to use only one constructor to make everything; maybe, your eyes have appeared such a code: operator * Return one "in the function inside the function Quote for defined static Rational objects:

// Write the third error method of this function inline const rulingal & operator * (Const Rational & LHS, Const Rational & Rhs) {static runch; // will be used as a reference to // static object

LHS and RHS multiplied, and the result is put in Result;

Return Result;}

This method seems to have a play. Although you will find that when you actually implement the pseudo code above, you will not call a Rational constructor that is impossible to give the correct value of Result, and avoid such calls is the theme we have to talk about. . Even if you realize the pseudo code above, you are smart and you can't eventually save this unfortunate design. Want to know why, look at the following, write a very reasonable user code:

Bool Operator == (Const Rational & LHS, // Rationals Operator == Const Rational & RHS); //

Rational A, B, C, D;

...

IF ((a * b) == (c * d)) {

Handling equal condition;

} else {

Treatment of non-equal;

}

Have you seen it? ((A * b) == (C * D)) will always be true, regardless of what is A, B, C, and D!

Rewrite the equal judgment statement in the form of an equivalent function, it is easy to understand the cause of this marine behavior:

IF (Operator == (Operator * (A, B), Operator * (C, D))))

Note When the operator == is called, there are always two Operator * just called, each call returns the reference to the internal static Rational object within the Operator *. Thus, the above statement is actually requesting Operator == to compare the "Operator * internal static Rational Rational object" and "the value of the static Rational object of" Operator * ", this is not equal to it!

Fortunately, I have the above instructions should be sufficient to convince you: "I want to" return a reference in the function of Operator * "actually waste time. But I am not naive, I believe that luck will always come. Some people - you know who these people do - I will think about it, "Hey, the method above, if a static variable is not enough, maybe you can use a static array ..."

Please hit this! Are we not enough?

I can't let yourself write a sample code is too high, because even if you only have the above ideas, it is ashamed. First, you have to choose a n, specify the size of the array. If N is too small, there will be no place to store the function return value, which has not improved compared to the "design of single static variable" in the previous negative. If N is too large, it will reduce the performance of the program because each object is created in the first time the array is called. This will bring N constructors and the overhead of n-analyte functions, even if this function is called once. If "Optimization" refers to the process of improving the performance of the software, then this practice can now be called "pessimization". Finally, think about how to put the need to the value of the array and how much overhead is needed? The most direct way to pass the value of the object is to assign a value, but how big is the value of assignment? In general, it is equivalent to calling a destructive function (destroyed old value) plus calling a constructor (copy new value). But our current goal is to avoid the development of constructors and sectations! In the face of reality: This method is absolutely unique.

So, the correct way to write a function that must return a new object is to let this function returns a new object. For Rational's Operator *, this means that it is not the following code (that is the code that originally saw), or it is essentially equivalent to the same price: Inline const rueal operator * (Const Rational & LHS, Const Rational & Rhs) {Return Rational (lhs.n * rhs.n, lhs.d * rhs.d);

Indeed, this will result in the "Operator *'s return value constructor and the expenses brought by the sector", but in the final analysis, it is just a correct procedure to run the correct program. Moreover, the overhead you worry can never appear: The return value will be safely removed (see Terms M20). When the compiler uses this optimization (the current most compiler is done), the program continues to work as before, but it is only the speed than you expected.

The above discussion can be attributed to: When you need to make a decision between returns a reference and return object, your duty is to select the one you can complete. As for how to make this choice as small as small, it is a matter of the manufacturer of the compiler.

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

New Post(0)