By default, C ++ passes an object into or out of a function by passing values (this is a feature inherited from C ). Unless you specify other methods, the function parameters will be initialized by copying the actual parameters (actual argument), and the function caller will receive a copy of the function return value. This copy is generated by the object copy constructor. This makes pass-by-value a costly operation. For example, consider the following class hierarchy:
Class person ...{
Public:
Person (); // parameters omitted for simplicity
Virtual ~ Person (); // see item 7 for why this is virtual
...
PRIVATE:
STD: string name;
STD: String address;
};
Class student: public person ...{
Public:
Student (); // parameters again omitted
~ Student ();
...
PRIVATE:
STD: String schoolname;
STD: String schooladdress;
};
Now, consider the following code. Here we call a function -- validatestudent, which gets a student parameter (by passing the value) and returns whether it verifies the valid result:
Bool validatestudent s; // function taking a student
// By Value
Student Plato; // Plato studied under Socrates
Bool platoisok = validatestudent (Plato); // call the Function
What will happen when this function is called?
Obviously, student's copy constructor is called and Plato is used to initialize the parameter S. Similarly, when validatestudent returns, s will be destroyed. Therefore, the parameter transfer cost of this function is the call of a copy constructor of student and the call of a destructor of student.
But this is not all. A student object contains two string objects. Therefore, each time you construct a student object, you must also construct two string objects. A student object must be inherited from a person object. Therefore, each time you construct a student object, you must also construct a person object. A person object contains two additional string objects. Therefore, the structure of each person also bears the construction of the other two strings. In the end, the consequence of passing a student object by passing the value is to call the student copy constructor and the person copy constructor, and four string copy constructor calls. When a copy of the student object is destroyed, each constructor call corresponds to a constructor call, therefore, the full cost of passing a student by passing values is six constructors and six destructor!
Well, this is correct and worthwhile. After all, you want all your objects to be reliably initialized and destroyed. Even so, if there is a way to bypass all these constructor and destructor, it should be better: Pass the reference to const (pass by reference-to-const ):
Bool validatestudent (const student & S );
This is very effective: No constructor or destructor are called because no new object is constructed. The const in the modified parameter Declaration is very important. The original version of validatestudent accepts a student value parameter, so the caller knows that they shield the function from any possible changes to the student they pass in; validatestudent can only change one copy of it. Now student is passed as a reference and declared as a const. Otherwise, the caller must worry that validatestudent has changed the student they passed in.
Passing parameters by reference also avoids the slicing problem ). When a derived class object is passed as a base class Object (value passing method), the copy constructor of the base class is called, the special features that make the object behave like a derived class object are "disconnected. You have only one pure base class object left-That's nothing to be surprised, because it was created by a base class constructor. This is almost not what you want. For example, suppose you work on a group of classes that implement a graphic Window System:
Class window ...{
Public:
...
STD: string name () const; // return name of window
Virtual void display () const; // draw window and Contents
};
Class required wwithscrollbars: public window ...{
Public:
...
Virtual void display () const;
};
All window objects have a name. You can get it through the name function, and all windows can be displayed. You can call the display function to do this. The fact that the display is virtual clearly tells you that the display method of a pure base class window object may be different from the display method of a dedicated javaswwithscrollbars object.
Now, suppose you want to write a function to print the name of a window and then display the window. The following function is incorrectly written:
Void printnameanddisplay (window W) // incorrect! Parameter
...{
// May be sliced!
STD: cout <W. Name ();
W. Display ();
}
Consider what will happen when you call this function using a javaswwithscrollbars object:
Descriwwithscrollbars wwsb;
Printnameanddisplay (wwsb );
The parameter W will be constructed as a window object -- it is passed, remember? In addition, the special information of the wwsb object is cut off. In printnameanddisplay, regardless of the type of the object passed to the function, W will always behave like a window object (because it is a window object ). In particular, the call to display in printnameanddisplay always calls window: display. It will never be called during wwithscrollbars: display.
The method to bypass the disconnection problem is to pass W by referencing it to the const:
Void printnameanddisplay (const window & W) // fine, parameter won't
...{
// Be sliced
STD: cout <W. Name ();
W. Display ();
}
Now w will look like the window actually passed in.
If you open the header of the compiler and peek at it, you will find that using a pointer to implement reference is a very typical practice, so passing something by reference usually means passing a pointer. It can be concluded that if you have an object of built-in type (for example, an int), passing it by value is often more efficient than passing the reference method. Therefore, for built-in types, when you need to make a selection between passing values and passing references to the const, there is no reason not to choose to pass values. The same recommendation applies to iterators and function objects in STL, because, as a convention, they are designed for passing values. The implementation of iterators and function objects has the responsibility to ensure efficient copying and not to be affected by the disconnection problem. (This is an instance of "how rules change, depending on which part of C ++ you use .)
The built-in types are small, so someone determines that all the small types are waiting for the selected values, even if they are user-defined. Such inferences are unreliable. Because an object is small, it does not mean that calling its copy constructor is cheap. Many objects-most STL containers in them-are the same as pointers, but copying such objects must copy everything they point to at the same time. It may be very expensive.
Even if a small object has a cheap copy constructor, there will be performance problems. Some compilers do not treat internal and user-defined types equally, even if they have the same underlying representation. For example, some compilers refuse to put an object consisting of only one double into a register, even if they prefer to put a pure double in general. If this happens, it is better to pass such an object as a reference, because the compiler will naturally put a pointer (referenced implementation) into a register.
A small user-defined type is not necessarily another reason for waiting for the selected value to be passed: as a user-defined type, its size often changes. A smaller type may become larger in future versions because its internal implementation may change. Even when you change to a different C ++ implementation, things may change. For example, when I write in this way, the implementation of the string type in some standard libraries is seven times the size of other implementations.
Generally, you can reasonably assume that only the built-in type and the iterator and function object type in STL are supported for low-cost data transfer. For any other type, follow the suggestions of this item and use the reference to replace the value.
Things to remember
· Replace the passed value with the passed reference to the const. In typical cases, it is more efficient and can avoid disconnection problems.
· This rule does not apply to built-in types and the iterator and function object types in STL. For them, passing values is usually more appropriate.