Clause 20: Prefer to replace pass-by-value with Pass-by-reference-to-const
By default, the C + + function pass parameter is the way to inherit C, which is the value pass (pass by value). This passes through a copy of the actual argument, which is created by invoking the copy constructor. Sometimes creating replicas is costly. For example, the inheritance system
Class Person{public:person (); virtual ~person (); private:std::string name;std::string address;}; Class Student:public person{public:student (); ~student (); private:std::string schoolname;std::string schoolAddress ;};
Now consider a function validatestudent, which requires a student argument, passed as pass by value.
BOOL Validatestudent (Student s);//pass by Valuestudent Plato;bool Platisok=validatestudent (Plato);
When the function is called, the copy constructor is called and the S is constructed with Plato. On return, S is refactored. Then the cost of pass by value is the one-student and one-time destructor. But what happens when the student constructs and reconstructs? There are two string objects inside it, so there are two string objects that are constructed and refactored. Student inherits from the person, plus the construction and destruction of the person, and there are two string objects inside the man, so we add 2 string objects to the construction and destruction. The total is six constructs and six destructors.
Pass by value is correct, but it is inefficient. Pass by Reference-to-const Way, you can avoid all constructors and destructors.
BOOL Validatestudent (const student& s);
This way of passing, no new objects are created, so naturally there is no invocation of constructs and destructors. parameter, the const modifier is more important, the original pass by value, the original value will not be modified naturally. Now passed as pass by reference, the object used within the function validatestudent and the same object passed in, in order to prevent modifications within the function, plus the const limit.
Pass by reference, you can also avoid object cutting (slicing) problems. A derived class (the derived class) object is passed as pass by value, and when treated as a base class object, the copy constructor of the base class object is called, at which point all the derived classes are cut off, leaving only a base Class section.
At the bottom of the C + + compiler, reference is often implemented as pointers, so pass by reference usually means that a real pass is a pointer. However, for built-in types, pass by value is often more efficient than pass by reference. So when using STL functions and iterators, it is customary to design pass by value. When designing iterators and functions, it is the designer's responsibility to see which delivery method is more efficient and whether there is a problem with the cut. The change in this rule applies to what part of C + + you use. (clause 1)
Typically, built-in types are small, so some people recognize that a small types is suitable for pass by value, and so does the user's own defined class. However, small objects do not mean that the copy constructor is less expensive. Many objects, including STL containers, have a member that is only one or two pointers, but when copying such objects, it is likely to be expensive to copy everything that the pointer points to.
There is also a reason why some compilers treat the "attitude" of built-in types and user-defined types differently, even if they have the same underlying description. For example, some compilers refuse to put an object consisting of a double into a buffer, but are willing to do so on a bare doubles on a regular basis. When this happens, the object should be passed in by reference, because the compiler will of course put the pointer in the buffer.
User-defined small types, which may also change, may become larger in the future, and its internal implementation may change, so a user-defined small type is prudent when using pass by value.
In general, it can be assumed that built-in types and STL iterators and function objects with pass by value are inexpensive. Other times it is best to replace pass by value with pass by reference to Const.
clause 21: When you must return an object, don't be paranoid about returning it reference
After mastering pass by reference, I began to wholeheartedly want to replace all pass by value with pass by reference. This often makes a mistake: passing some reference points to objects that do not exist. Consider a class that is used to represent the product of rational numbers.
Class rational{public:rational (int numerator=0, int denominator=1), ... private:int N, d;friendconst Rational operator* ( Const rational& LHS, const rational& RHS);};
This version of operator* returns its computed result (object) in the form of by value. The cost of this return is the creation of one object + Destruction + another object.
The creation of an object means that in this operator* function, a new object is created to hold the result, which is then returned with this new object, which is used to initialize the other object. This new object is then destructor.
But if passed by reference, there will be no cost. But reference is just a name, representing an already existing object, and any time you see reference should ask yourself, what's another name for it? If the above operator* returns a reference, then it must point to an existing rational object.
Rational A (1, 2);//1/2rational B (3, 5);//3/5rational c=a*b;//3/10
This returns a rational object with a value of 3/10. However, this object does not exist at this point, and if you return reference, you must create the rational object within the function operator*.
A function creates a new object in two ways, either on a stack or on a heap. If you define a local variable, create it on the stack
Const rational& operator* (const rational& LHS, const rational& RHS) {Rational result (lhs.n* RHS.N, lhs.d* RHS. d); return result;}
Above, there is no way to avoid calling constructors, and result is constructed by constructors like any object. There is another error: This function returns reference execution result, but result is a local object that is destroyed before the function exits.
Consider creating an object on the heap
Const rational& operator* (const rational& LHS, const rational& RHS) {rational* result=new Rational (lhs.n* RHS . N, lhs.d* rhs.d); return *result;}
Or the cost of a constructor, the allocated memory will be initialized with an appropriate constructor function. Now there is another question: who is responsible for implementing delete for your new object?
Even if the customer is using caution, it is possible to cause the following memory leaks:
Rational w,x,y,z;w=x*y*z;
The above statement calls two times operator*, uses two times new, and requires two delete. But there is no way for the user to perform those delete operations, because they do not have the pointer behind the reference returned by operator*. This can cause a memory leak.
In both of the above practices (both in the stack and the creation of objects on the heap), the constructor is punished because operator* returns the result of the call. Our initial goal was to avoid the invocation of such a constructor function. There is another way to avoid calls to any constructor: let operator* return a pointer to the static rational object defined inside the function:
Const rational& operator* (const rational& LHS, const rational& RHS) {static Rational result;result= ...; return result;}
Let's not say what's wrong with the above code in multi-threading, first look at what's wrong with the following call:
BOOL operator== (const rational& LHS, const rational& RHS);//Rational A, B, C, D;......if ((a*b) = = (c*d) written for the comparison of rational objects ) {//The product is equal, dosomething ();} else//Product Unequal Time {dootherthing ();}
The above expression (a*b) = = (C*d) always returns TRUE. Because the objects returned by operator* are static objects that are defined inside operator*. This object has only one, when the latter is computed, the former is overwritten. So there is always a comparison of two rational objects of the same kind.
The correct way to get a function that must return a new object is to have that function return a new object. It's so simple. The above operator* correct wording:
inline const Rational operator* (const rational& LHS, const rational& RHS) {return rational (lhs.n* RHS.N, lhs.d* RHS . D);}
In this way, you need to withstand the construction and analysis of the operator* return value, but in the long run it is only a small price to pay for the right behavior. But if the price is scary and can't afford it, don't forget that C + + and all programming languages allow compiler implementations to perform optimizations that improve the efficiency of the output code without altering its observable behavior. If the compiler is performing optimizations, your programs will keep their behavior, but run faster than expected.
The above can be summed up as follows: When choosing between returning a reference and returning an object, pick the one that behaves correctly. Let the compiler manufacturer minimize the cost for you!
"Effective C + +" resource management: clause 20-clause 21