Which methods are invoked in the process of object usage?
Code example 1
class Test { public: Test(int a = 10) :ma(a) { cout << "Test(int)" << endl; } ~Test() { cout << "~Test()" << endl; } Test(const Test &t) :ma(t.ma) { cout << "Test(const Test&)" << endl; } Test& operator=(const Test &t) { cout << "operator=" << endl; ma = t.ma; return *this; } private: int ma; };
Many people may think: Test t4=Test(20); The temporary object is constructed first, then the temporary object is copied and constructed t4, and then the statement ends and the temporary object is destructed.
Is that so?
Let's run it
t1 is to call the normal constructor. Both t2 and t3 call the copy constructor.
t4 is to call the normal constructor. It's not what we just said.
int main() { Test t1;//Call constructor Test t2(t1);//Call copy constructor Test t3 = t1;//Call the copy constructor because t3 has not been generated //Test(20) shows that the generated temporary object has no name, so its life cycle: the statement //At the end of the statement, the temporary object is destructed /* C++Compiler optimization of object construction: when generating new objects with temporary objects, temporary objects No, just construct a new object */ Test t4 = Test(20);//And Test t4(20); No difference! cout << "--------------" << endl; return 0; }
We'll see it later
int main() { Test t1;//Call constructor Test t2(t1);//Call copy constructor Test t3 = t1;//Call the copy constructor because t3 has not been generated Test t4 = Test(20);//And Test t4(20); No difference! cout << "--------------" << endl; //t4.operator=(t2) t4 = t2;//Call assignment function because t4 already exists //Test(30) explicitly generates temporary objects //t4 already exists, so it is not constructed. This temporary object must be constructed and generated //After the temporary object is generated, assign a value to t4 //After the statement is issued, the temporary object is destructed //t4.operator=(const Test &t) t4 = Test(30); cout << "--------------" << endl; return 0; }
We'll see it later
int main() { Test t1; Test t2(t1); Test t3 = t1; Test t4 = Test(20); cout << "--------------" << endl; t4 = t2;//t4 call the assignment overloaded function t4 = Test(30); //The temporary object is explicitly generated and assigned to t4. After the statement is issued, the temporary object is destructed t4 = (Test)30;//Convert top 30 to test type int - > Test (int) //When converting other types to class types, the compiler looks at this class type //Whether there is an appropriate constructor to convert an integer to Test depends on whether this class type has //Constructors with int type parameters, if any, can explicitly generate temporary objects, and then //After assignment to t4, the temporary object destructs //The temporary object is generated implicitly, and then assigned to t4. After the statement is issued, the temporary object is destructed t4 = 30; //Convert the integer to test, test (30) int - > Test (int) char * - > Test (char *) cout << "--------------" << endl; return 0; }
We'll see it later
int main() { Test t1; Test t2(t1); Test t3 = t1; Test t4 = Test(20); cout << "--------------" << endl; t4 = t2; t4 = Test(30); t4 = (Test)30; t4 = 30 cout << "--------------" << endl; Test *p = &Test(40);//The pointer points to a temporary object, which must be generated //Then p points to the address of the temporary object //After the statement is issued, the temporary object is destructed //At this time, p points to a destructed temporary object, and p is equivalent to a wild pointer const Test &ref = Test(50);//Reference a temporary object, which is also to be generated //After the statement is issued, the temporary object is not destructed because the reference is equivalent to an alias. The temporary object is destructed because it has no name //It is safe to reference a temporary object with a reference variable. The temporary object has a name, and the life cycle of the temporary object becomes a reference variable //Life cycle. The reference variable is a local variable of this function. The temporary object is destructed only after return cout << "--------------" << endl; return 0; }
Code example 2
class Test { public: //Because a and B have default values, there are three ways to construct: //Test() Test(10) Test(10, 10) Test(int a = 5, int b = 5)//Constructor :ma(a), mb(b) { cout << "Test(int, int)" << endl; } ~Test()//Destructor { cout << "~Test()" << endl; } Test(const Test &src)//copy constructor :ma(src.ma), mb(src.mb) { cout << "Test(const Test&)" << endl; } void operator=(const Test &src)//Assignment function { ma = src.ma; mb = src.mb; cout << "operator=" << endl; } private: int ma; int mb; };
//Object construction order ID: 1,2,3... 14 Test t1(10, 10);//1.Test(int, int) int main() { Test t2(20, 20);//3.Test(int, int) Test t3 = t2;//4.Test(const Test&) //static Test t4(30, 30) is initialized only after it is run for the first time; static Test t4 = Test(30, 30);//5.Test(int, int) t2 = Test(40, 40);//6.Test(int, int) operator = call ~ Test() //(50,50) is a comma expression, (expression 1, expression 2, expression n) //The final result of (50,50) is the result of the last expression n //(50, 50) = (Test)50; Test(int) t2 = (Test)(50, 50);//7.Test(int,int) operator = call ~ Test() t2 = 60;//Test(int) 8.Test(int,int) operator = call ~ Test() Test* p1 = new Test(70, 70);//9. Test(int,int) needs to call delete to destruct the object Test* p2 = new Test[2];//10. Test(int,int) Test(int,int) needs to call delete to destruct the object Test* p3 = &Test(80, 80);//11. Test(int,int) calls ~ Test() const Test& p4 = Test(90, 90);//12. Test(int,int) delete p1;//13.~Test() delete[]p2;//14. ~Test() ~Test() } Test t5(100, 100);//2.Test(int, int)
Code example 3
class Test { public: //There are default values. There are two construction methods: test() test (20) Test(int data = 10) :ma(data) { cout << "Test(int)" << endl; } ~Test() { cout << "~Test()" << endl; } Test(const Test &t):ma(t.ma) { cout << "Test(const Test&)" << endl; } void operator=(const Test &t) { cout << "operator=" << endl; ma = t.ma; } int getData()const { return ma; } private: int ma; };
This method receives an object of Tesl type, then takes out the value ma of the Test object and assigns it to val, then takes the val value to the local object in the constructor, and then returns the local object.
The pointer or reference cannot be returned here, because to return the pointer or reference, it is necessary to ensure that the object still exists at the end of the function. If the object does not exist, the pointer indirectly accesses the memory of the object, which is illegal. Therefore, the address or reference of a local object or temporary object cannot be returned.
Cannot return a pointer or reference to a local or temporary object
The address of the following object can be returned in the local function:
Because this object is in the data segment, the memory will be available when the program runs. When it runs to it for the first time, construct this object, and it will not destruct the object until the whole program runs.
Let's continue to analyze the following code:
What is the result of printing when we run?
#include <iostream> using namespace std; class Test { public: //There are default values. There are two construction methods: test() test (20) Test(int data = 10) :ma(data) { cout << "Test(int)" << endl; } ~Test() { cout << "~Test()" << endl; } Test(const Test& t) :ma(t.ma) { cout << "Test(const Test&)" << endl; } void operator=(const Test& t) { cout << "operator=" << endl; ma = t.ma; } int getData()const { return ma; } private: int ma; }; Test GetObject(Test t) { int val = t.getData(); Test tmp(val); return tmp; } int main() { Test t1;//1. Call constructor with integer arguments Test t2;//2. Call constructor with integer arguments t2 = GetObject(t1);//Function call, argument passed to formal parameter, initialization or assignment? //Of course, it is initialization. Object initialization is to call the constructor. Assignment is that both objects have a = overload that calls the object on the left //t1 is the constructed Test object, while t is the Test object being defined //3. Call test (const test &) to copy the construction parameter t with t1 //4. Call the construct of Test(int), construct the tmp object, and then return tmp;tmp cannot directly assign a value to t2 //Because tmp and t2 are objects on two different function stack frames, they cannot be assigned directly. When the GetObject function completes the call //The tmp object is destructed as a local object. In order to bring out the return value, click return tmp; Here, first set the frame in the main function stack //The purpose of building a temporary object on is to bring out the tmp object, //5. Call test (const test &), and tmp copies and constructs the temporary object on the main function stack frame //6. GetObject scope, tmp destruct //7. Parameter t object deconstruction //8. operator =, assign the temporary object just constructed by the main function to t2. The temporary object has no name and will be destructed when a statement is issued //9. Destruct the temporary object just constructed by the main function //10. End of main function, t2 destruct //11. t1 destruct return 0; }
Summarize three rules for object optimization
- In the process of passing function parameters, objects are passed by reference first, so that a copy construction call of formal parameter t can be omitted. The formal parameter does not build a new object and does not need to be destructed out of the scope, so do not pass by value!
- When a function returns an object, it should first return a temporary object rather than a defined object
- When receiving a function call whose return value is an object, it is preferred to receive it in the way of initialization rather than assignment
Let's look at the following code example
How can we optimize the above code?
1. In the process of passing function parameters, objects are passed by reference first, so that a copy construction call of formal parameter t can be omitted. If the formal parameter does not build a new object, there is no need to destruct out of the scope, so do not pass by value!
There is no copy structure for t1, no new object for parameter t, and no destruct for out of scope.
The copy structure of formal parameter T and the deconstruction of formal parameter t are omitted
2. When a function returns an object, it should first return a temporary object rather than a defined object
Both t1 and t2 call constructors with integer parameters.
Then, the parameters t1 to t are passed by reference, and no object is generated.
return Test(val); The returned object is a temporary object. What we want is to call a constructor with integer parameters to generate a temporary object. If the temporary object in the statement cannot get out of the GetObject function, the temporary object will be destructed as soon as the GetObject function ends and the stack frame returns. Therefore, in order to take this temporary object out, you can only use this temporary object copy to construct a new temporary object on the stack frame of the main function.
However, when a new object is constructed by copying a temporary object, the C + + compiler will optimize it. The temporary object will not be generated, but the new object will be constructed directly by generating a temporary object.
Therefore, return Test(val); This temporary object is not generated.
You can construct this temporary object directly on the stack frame of the main function.
Therefore, the scope of the GetObject function is not needed to destruct the local object. Because this temporary object was not generated.
Then, take the temporary object of the main function stack frame to assign value to t2. After the statement is issued, the temporary object on the main function stack frame is destructed.
Then there is t2 destruct, t1 destruct.
In this optimization, the structure and Deconstruction of tmp are missing.
3. When receiving a function call whose return value is an object, it is preferred to receive it in the way of initialization rather than assignment
1. Call the normal constructor to construct t1
2. Is the process of initializing t2. Process the GetObject function call first. No object was generated from argument to formal parameter.
3,return Test(val); Only temporary objects on the main function stack frame are generated
4. Then initialize t2 with this temporary object! Use this temporary object copy to construct a new object t2 of the same type. The C + + compiler will optimize. The temporary objects on the main function stack frame will not be generated, and the t2 object will be constructed directly.
return Test(val); Construct t2 objects directly
Test t2= GetObject(t1); In the assembly, in addition to passing in the address of T1, the address of T2 is also passed in and pressed on the function stack frame, so return Test(val); You can get the address of T2 and know which memory to construct an object named T2.
Then destruct t2
Then destruct t1
Test GetObject(Test &t) { int val = t.getData(); return Test(val); } int main() { Test t1; Test t2= GetObject(t1); return 0; }