Reading Notes effctive c ++ Item 20 is preferentially transmitted by const-reference (by-reference-to-const) instead of by value (by value), constreference
1. Passing parameters by value will be efficient
By default, C ++ transfers objects to or from a function by pass by value (a typical feature inherited from C ). Unless you specify other methods, the function parameter will be initialized by copying the actual parameter value, and the function caller will get a copy of the function return value. These copies are generated by the object copy constructor. This makes pass-by-value an expensive operation. For example, consider the following class inheritance system (Item 7 ):
1 class Person { 2 3 public: 4 5 Person(); // parameters omitted for simplicity 6 7 virtual ~Person(); // see Item 7 for why this is virtual 8 9 ...10 11 private:12 13 std::string name;14 15 std::string address;16 17 };18 19 class Student: public Person {20 21 public:22 23 Student(); // parameters again omitted24 25 virtual ~Student();26 27 ...28 29 private:30 31 std::string schoolName;32 33 std::string schoolAddress;34 35 };
Now let's take the following code into consideration. Here we call a function, validateStudent, which has a Student parameter (by value). The return value indicates whether the verification is successful:
1 bool validateStudent(Student s); // function taking a Student2 3 // by value4 5 Student plato; // Plato studied under Socrates6 7 bool platoIsOK = validateStudent(plato); // call the function
What happens when a function is called?
It is clear that the Student copy constructor will be called and plato will be used to initialize the parameter s. It is also clear that s will be destroyed when the validateStudent function returns. Therefore, the overhead of passing this function parameter is to call the constructor and the Destructor respectively.
But this is not all overhead. A Student object has two string objects, so every time you build a Student object, you must construct two string objects. Student objects inherit from Person objects, so every time you build a Student object, you must construct a Person object. A Person object has two additional string objects, so each Person constructor also needs to construct two additional strings. The final result is to pass a Student object by value, resulting in a call to copy the constructor to Student, a call to copy the constructor to Person, and four calls to copy the constructor to stirng. When the copy of the Student object is released, the corresponding destructor of each constructor will be called, therefore, the total overhead of passing a Student object by value is 6 constructor and 6 constructor !!
2. It is more efficient to pass by const reference
This is a correct and satisfactory behavior. After all, what you need is that all objects are reliably initialized and destroyed. In addition, it would be better to bypass these constructor and destructor in one way.This method exists: PressConstPass by reference-to-const).
1 bool validateStudent(const Student& s);
This method is more efficient:No constructor or destructor calledBecause no new object is created. In the parameter Declaration of the revised version, const is very important. The original version of validataStudent has a value-based passing Studetn parameter. The caller will know that any possible modifications to the passed Student parameter will be blocked; validateStudent is only modifying a copy of it. Now Student is passed according to reference and declared as const is also required. Otherwise, the caller will worry about whether the passed parameter is modified.
3. Pass by const to avoid slicing Problems
Passing parameters by reference also avoids slicing problems. When a derived class object is passed as a base class Object (passed by value), the copy constructor of the base class is called, "make the object behavior look like a derived class object" this particular feature is "cut off. Only one base class object is left for you, because it is created by a base class constructor. This is what you never want to see. For example, if you are working on some classes, these classes implement a graphical Window System:
1 class Window { 2 3 public: 4 5 ... 6 7 std::string name() const; // return name of window 8 9 virtual void display() const; // draw window and contents10 11 };12 13 class WindowWithScrollBars: public Window {14 15 public:16 17 ...18 19 virtual void display() const;20 21 };
All window objects have a name. You can use the name function to obtain them, and all the windows can be displayed. You can use the display function to trigger them. The fact that the Display function is a virtual function tells you that the Display method of Basic Windows objects is different from that of other wwithscrollbars objects (Item 34 and Item 36 ).
Now suppose you have implemented a function. Print the window name and display the window. The following is an incorrect method for implementing such a function:
1 void printNameAndDisplay(Window w) // incorrect! parameter2 3 { // may be sliced!4 5 std::cout << w.name();6 7 w.display();8 9 }
Consider what will happen when you call this function using a javaswwithscrollbars object as a parameter:
1 WindowWithScrollBars wwsb;2 3 printNameAndDisplay(wwsb);
The parameter w will be constructed, and it is passed by value. Therefore, as a Window object, all the specific information that makes wwsb look like a wide wwithscrollbars object will be removed. Inside printNameAndDispay, w always acts like a Window object (because it is a Window object), regardless of the parameter type of the input function. In particular, the call to display within printNameAndDisplay always calls Window: display and never calls javaswwithscrollbars: display.
To solve the slice problem, PASS w by const reference (by reference-to-const ):
1 void printNameAndDisplay(const Window& w) // fine, parameter won’t2 3 { // be sliced4 5 std::cout << w.name();6 7 w.display();8 9 }
Now w's behavior is consistent with the actual type of the input parameter.
4. Under what circumstances is it reasonable to pass by value?
If you peek at the underlying layer of the C ++ compiler, you will find that the reference is implemented by Pointer. Therefore, passing something by reference means passing a pointer. Therefore, if you have a built-in type object (such as int), passing by value is more efficient than passing by reference. For built-in types, it is reasonable to select the pass by value option when you select between pass by value and pass by reference. This applies to iterators and function objects in STL, because they are designed to pass by value by convention. The designers of the iterator and function object have the responsibility to pay attention to the following two problems: Efficient copying and no need to endure slicing. (This is an example of how a rule is changed, depending on which part of the C ++ you use can be found in Item 1 .)
5. If the object is not small, it should be passed by value.
Built-in types occupy a small amount of memory, so some people come to the conclusion that all such small types are candidates for passing by value, even if they are user-defined types. The reason is unreliable. Because an object occupies less memory, it does not mean that calling its copy constructor is not expensive. Many objects-most STL containers in these objects-only contain one pointer, but copying these objects copies everything they point. This is a very expensive operation.
Performance problems occur even when the calling of the copy constructor of a small object is very low. Some compilers treat built-in and custom types differently, even if they have the same underlying representation (underlying representation ). For example, some compilers refuse to put objects containing only one double value into the cache, but are happy to do so for a naked double value. When this happens, it is better to pass these objects by reference, because the compiler will put the pointer (the implementation of the Reference) into the cache.
Another reason why a small user-defined type is not a good candidate for passing by value is that, as a user-defined type, their size will change. A type may be small now, but may become larger in the future, because its internal implementation may change. When you switch to a different C ++, the current situation may also change. For example, some implementations of the string type in the standard library are six times larger than other implementations.
Generally, you can make a reasonable assumption that the unique type of "passing by value is not expensive" is the built-in type, STL iterator, and function object. For any other type, we recommend that you pass by const instead of by value if you follow this clause.
6. Summary
- Pass by const-reference instead of by value. It is more efficient and can avoid slicing problems.
- This rule does not apply to built-in types, STL iterators, and function object types. For them, passing by value is usually appropriate.