C LanguageC ++ inherits this tradition and uses it as the default method. Unless explicitly specified, the function parameters always pass
"Copy of real parameters" for initialization. The caller of the function also obtains the copy of the function return value.
As I pointed out in the introduction of this book, the specific meaning of "passing an object by value" is defined by the copy constructor of the class of this object. This causes
It becomes a very expensive operation. For example, let's look at the structure of the (just hypothetical) class below:
Code
Class Person {
Public :
Person (); // Parameters are omitted for simplicity.
//
~ Person ();
...
Private :
String Name, address;
};
Class Student: Public Person {
Public :
Student (); // Parameters are omitted for simplicity.
//
~ Student ();
...
Private :
String Schoolname, schooladdress;
};
Now we define a simple function returnstudent, which takes a student parameter (via value) and returns it immediately (also via value ). Call
Functions:
Student returnstudent (student s) {return s ;}
Student Plato; // Plato (Plato) in
// Socrates (Socrates)
Returnstudent (Plato); // call returnstudent
In this seemingly irrelevant function call process, what exactly does it happen internally?
To put it simply, student's copy constructor is called to initialize s to Plato, and student's copy constructor is called
The return value object is initialized to S. Then, the destructor of S is called. Finally, the destructor of the returned object of returnstudent is called. So, what?
The cost of a function that has not been done is that the copy constructor of two student plus two student constructor.
But it's not over! The student object has two string objects, so each time you construct a student object, you must also construct two string objects. Student object
It is inherited from the person object, so a person object must be constructed every time a student object is constructed. One person object has two other objects.
So the construction of each person must be accompanied by the construction of the other two strings. Therefore, passing a student object through the value will eventually cause the call.
One student copy constructor, one person copy constructor, and four string copy constructor. When the student object is destroyed, each constructor pair
Call a destructor. Therefore, the final overhead of passing a student object through values is six constructor and six destructor. Because
The returnstudent function uses two values (one pair of parameters and one pair of return values). This function calls a total of twelve constructors and twelve destructor.
Count!
In the eyes of C ++ compiler designers, this is the worst case. The compiler can be used to eliminate some calls to the copy constructor (C ++ standard-see the Terms and Conditions.
50 -- describes the specific conditions under which the compiler can perform such optimization, and the M20 provides an example ). Some compilers also do this. However
When this is common in all compilers, be cautious about the overhead caused by passing objects through values.
To avoid this potentially expensive overhead, instead of passing objects through values, you must reference them:
Const student & returnstudent (const student & S)
{Return s ;}
This is highly efficient: No constructor or destructor is called because no new object is created.
Passing parameters through references has another advantage: it avoids the so-called "slicing problem )". When the object of a derived class acts as the base class
When an object is passed, its (derived class Object) behavior as a derived class will be "cut" and become a simple base class object. This
It is often not what you want. For example, assume that the following class is designed to implement the graphic Window System:
Class Window {
Public :
String Name () Const ; // Return window name
Virtual Void Display () Const ; // Draw window content
};
Class Required wwithscrollbars: Public Window {
Public :
Virtual Void Display () Const ;
};
Each window object has a name, which can be obtained through the name function. Each window can be displayed and can be implemented by calling the display function.
The display declaration is virtual, which means that a simple window-based Class Object is displayed in a way that is often the same as a very expensive javaswwithscrollbars object.
(For details, see section 36, 37, and M33 ).
Now let's assume that we write a function to print the window name and then display the window. The following is a function written in the wrong way:
// A function troubled by the cutting problem
Void printnameanddisplay (window W)
{
Cout <W. Name ();
W. Display ();
}
Imagine what will happen when you call this function using a javaswwithscrollbars object:
Descriwwithscrollbars wwsb;
Printnameanddisplay (wwsb );
Parameter W will be created as a Windows Object (it is passed by value, remember ?), All actions of wwsb
The behavior characteristics of the windowwithscrollbars object are all "cut. Inside printnameanddisplay, W acts like a window-like object.
(Because it is a window object), regardless of the type of the object that was originally uploaded to the function. In particular, the printnameanddisplay internal
The call to display is always window: Display, rather than wide wwithscrollbars: display.
The Method to Solve the cutting problem is to pass W through reference:
// A function that is not troubled by the "cutting problem"
Void printnameanddisplay (const window & W)
{
Cout <W. Name ();
W. Display ();
}
Now w's behavior is consistent with the actual type of the function to be uploaded. In order to emphasize that w cannot be modified within the function although it is passed by reference, it is necessary to adopt the establishment of Clause 21
Declare it as Const.
Transferring references is a good practice, but it will lead to its own complexity. The biggest problem is the alias problem, which is discussed in Clause 17. In addition
It is important that sometimes references cannot be used to transmit objects. For more information, see section 23. In the end, references are almost implemented through pointers.
The pass object is actually a pass pointer. Therefore, if a small object, such as int, is used to pass a value, it is more efficient than transferring a reference.