This section mainly talks about some precautions in the process of using and calling objects and functions. The more important ones are the right value reference and the last move and forward
What methods are called during the use of the object?
For the following test class, a dozen different definitions are listed
class Test {
public:
Test(int a = 4, int b = 10) : ma(a), mb(b) {
cout << "Test()" << endl;
}
~Test() {
cout << "~Test()" << endl;
}
Test(const Test &src) {
ma = src.ma;
mb = src.mb;
cout << "Test(const Test&)" << endl;
}
Test &operator=(const Test &src) {
ma = src.ma;
mb = src.mb;
cout << "operator=(const Test&)" << endl;
return *this;
}
private:
int ma;
int mb;
};
The results are as follows:
There are several noteworthy points:
- Object assignment will produce temporary objects, which will execute destructors after the end of the statement
- Implicitly generated temporary objects, such as
t2=60
, the compiler will find out whether there is an appropriate constructor in the object to generate the object - Save the temporary object with a pointer. The temporary object will be destructed after the end of the statement. It is safe to point to objects by reference
- (50,50) this form is a comma expression, which only looks at the last number when assigning a value
Methods called behind function calls
In the process of function call, the parameter passed to the formal parameter needs to be reinitialized, and the formal parameter object of the function needs to be initialized. In this process, the object will be calledCopy construction method。
The object returned inside the function body should also be in the main stack framecopy construction A temporary variable to access this object in the main scope.
After the function body is executed, you need to destruct the object constructed in the function body first, and then destruct the object constructed from the formal parameter list
Three rules of object optimization
- In the process of passing function parameters, objects are passed by reference first, not by value.
- When a function returns an object, it should give priority to returning a temporary object rather than a defined object
- When accepting a function call whose return value is an object, it is preferred to receive it in the way of initialization rather than assignment
The code in the figure above is finally optimized into the following code:
Test GetObject(Test &t){
int val=t.getData();
return Test(val);// Defining temporary objects 2 Test()
}
int main(){
Test t1;//1.Test()
Test t2=GetObject(t1);// Using temporary object copy to construct new objects of the same type, the compiler will optimize this process, reducing the construction and Deconstruction of temporary objects on the main stack frame
return 0;
}
//3.~Test()
//4.~Test()
After optimization, there are only four steps left to construct and destruct
Previous problems in string code
class String {
friend std::ostream &operator<(const String &str) const {
return strcmp(_pstr, str._pstr) > 0;
}
bool operator
There are many temporary objects in the call. To generate a temporary object, you need to copy and assign the original memory on the stack frame, and delete it once, which is very time-consuming
Add copy construction and assignment function with R-value reference parameter
An R-value reference variable itself is an l-value, so a defined R-value reference variable cannot be assigned to an R-value reference
The copy constructor and assignment overloaded function with R-value reference parameters will point to the memory opened up by the temporary object. There will be no invalid memory release and opening up in the whole process, which greatly improves the operation efficiency
The example code is as follows:
//Copy constructor with right value reference
String(String &&src) noexcept {
std::cout<
The output results are as follows:
Copy overloaded objects with lvalue references generally have copy overloaded versions with lvalue references.
Application of custom string class in vector
In push_ In the process of back, if an lvalue is passed in, the temporary object with lvalue parameters will be matched. If a temporary object is passed in, the constructor of the temporary object will be called first, and then the copy constructor with lvalue parameters will be called.
Why push_ Will back call the copy constructor with an R-value reference? Look down\(\Downarrow\)
Move semantic and forward type perfect forwarding
Move () converts an lvalue to an lvalue
Forward () refers to the perfect forwarding of types, which can identify left and right value types
If you define a push that supports calling an R-value reference in your defined vector class_ For the back method, first push_ The parameter of back is a type of right value reference
The first way to write:Using function overloading, respectively define a function whose parameter is an lvalue reference and a function whose parameter is an lvalue reference
void push_back(T &val) {
if (full()) {
expend();
}
//*_last++ = val;
_alloctor.construct(_last, val);
_last++;
}
void push_back(T &&val) {
if (full()) {
expend();
}
_alloctor.construct(_last, std::move(val));
_last++;
}
Called in the function clock_alloctor.construct()
, the function passes the parameter Val, so the function overload is also required to accept right value references and left value references.
Void construct (t * P, const T & VAL) {// responsible for object construction
new(p) T(val);// Locate new
}
Void construct (t * P, const T & & VAL) {// responsible for object construction
new(p) T(std::move(val));// Locate new
}
The second way is to use the type deduction and reference folding of function template
First, explain what reference folding means. If the type deduced by the function template is ty & & + & & (& & after + is a required symbol in the parameter), the folded type of reference is ty & &, which is a right value reference; If the type deduced by the function template is ty & + & &, the type after reference folding is ty &, which is an lvalue reference. Use forward to identify the type of left or right value of ty.
template
void push_ Back (TY & & VAL) {// ty identifies whether the incoming parameter is an lvalue or an lvalue, and then folds the reference
if (full()) {
expend();
}
_ alloctor. construct(_last, std::forward(val));// Convert Val to the type recognized by ty and avoid function overloading
_last++;
}
template
Void construct (t * P, ty & & VAL) {// ty identifies whether the incoming parameter is an lvalue or an lvalue, and then folds the reference
new(p) T(std::forward(val));// Convert Val to the type recognized by ty and avoid function overloading
}