Example analysis of the underlying difference between C + + copy initialization and direct initialization

Source: Internet
Author: User
Tags constructor

Read the instructions: For the previous code can not understand the friend, you may jump to the last to see the summary, and then look back at the above content, or there will be the feeling of the enlightened.





Development Run Environment: Visual Studio 2013





Source





#include <iostream>


#include <cstring>


using namespace Std;


Class Classtest


{


Public


Classtest ()


{


C[0] = ' the ';


cout << "Classtest ()" << Endl;


}


classtest& operator= (const classtest &CT)


{


strcpy (c, ct.c);


cout << "classtest& operator= (const classtest &ct)" << Endl;


return *this;


}


Classtest (classtest&& CT)


{


cout << "classtest (classtest&& CT)" << Endl;


}


Classtest & operator= (classtest&& CT)


{


strcpy (c, ct.c);


cout << "Classtest & operator= (classtest&& ct)" << Endl;


return *this;


}


Classtest (const char *PC)


{


strcpy (c, PC);


cout << "classtest (const char *pc)" << Endl;


}


Private


Classtest (const classtest& CT)


{


strcpy (c, ct.c);


cout << "classtest (const classtest& CT)" << Endl;


}


virtual int ff ()


{


return 1;


}


Private


Char c[256];


};


Classtest F1 ()


{


Classtest C;


return C;


}


void F2 (Classtest ct)


{


;


}


int main ()


{


Classtest ct1 ("AB");//Direct initialization


Classtest ct2 = "AB";//Copy initialization


Classtest ct3 = ct1;//Copy Initialization


Classtest Ct4 (CT1);//Direct initialization


Classtest ct5 = classtest ("AB");//Copy initialization


Classtest CT6 = F1 ();


F1 ();


F2 (CT1);


return 0;


}




Initialize 1:classtest ct1 ("AB")





Classtest ct1 ("AB");//Direct initialization


00b09518 Push 0b0dcb8h//"AB" string address


00B0951D Lea Ecx,[ct1]


00b09523 call Classtest::classtest (0dc101eh)





The above initialization assembly code, first the "AB" string address stack, and get the address of the Ct1 object into the register ecx, that is, through the stack and register passed two parameters, called the classtest (const char *pc) constructor. Initializes the Ct1 object with the address of the Ct1 object (the this pointer) in the classtest (const char *pc) function.





Initialize 2:classtest ct2 = "AB"





Classtest ct2 = "AB";//Copy initialization


00b09528 Push 0b0dcb8h//"AB" string address


00b0952d Lea Ecx,[ct2]


00b09533 call Classtest::classtest (0dc101eh)





This is a copy initialization, the bottom of the assembly is a bit unexpected. The right side of the assignment expression generates a temporary object using the constructor of the const char*, then copies or moves the temporary object to CT2, but the Visual Studio compiler handles the The string to the right of the assignment expression is initialized directly to the CT2 as the arguments of the constructor, and is initialized with 1, which omits a step, speeds up the operation, and achieves the same effect. Note: In the above assembly, Visual Studio compiler optimizations have been turned off, indicating that this approach has been used as a general method of Visual Studio, rather than as a vs-perceived optimization tool.





Initialize 3:classtest ct3 = Ct1





Classtest ct3 = ct1;//Copy Initialization


00b09538 Lea Eax,[ct1]


00b0953e push EAX


00b0953f Lea ECX,[CT3]


00b09545 call Classtest::classtest (0DC14C4H)





Initialization 3 passes the stack and register ECX to the object address on both sides of the assignment expression. The copy constructor of the class is then called (Note: the function has only one formal parameter, but it also passes in the address of the Ct3 object, this pointer), if the user does not define a copy constructor, The compiler generates a composite copy constructor. As follows:







010B3EE0 Push EBP


010B3EE1 mov Ebp,esp


010b3ee3 Sub Esp,0cch


010B3EE9 push EBX


010b3eea push ESI


010b3eeb Push EDI


010B3EEC push ECX


010b3eed Lea EDI,[EBP-0CCH]


010B3EF3 mov ecx,33h


010B3EF8 mov eax,0cccccccch


010B3EFD Rep STOs dword ptr Es:[edi]


010b3eff pop ECX


010B3F00 mov dword ptr [THIS],ECX


010B3F03 mov Eax,dword ptr [this]//eax point to Ct3 object address


010B3F06 mov dword ptr [eax],10bdc70h//virtual table pointer stored where object offset is 0


010B3F0C mov esi,dword ptr [__that]//esi store Ct1 object address


010b3f0f Add esi,4//Add ESI 4, skip 4-byte virtual table pointer, point to member variable C after CT1


010B3F12 mov Edi,dword ptr [this]


010b3f15 Add edi,4//edi point to CT2 after member variable C


010B3F18 mov ecx,40h


010B3F1D Rep movs dword ptr es:[edi],dword ptr [esi]///copy of character array elements in Ct1 to ct3 character array


010B3F1F mov Eax,dword ptr [this]//Return CT3 object address via EAX


010b3f22 Pop EDI


010b3f23 pop ESI


010b3f24 pop ebx


010B3F25 mov esp,ebp


010b3f27 Pop EBP





Initialization of 4:classtest Ct4 (CT1)





Classtest Ct4 (CT1);//Direct initialization


010B954A Lea Eax,[ct1]


010b9550 push EAX


010b9551 Lea Ecx,[ct4]


010b9557 call Classtest::classtest (0DC14C4H)





Initializing 4 and initializing the 3 assembly instructions, the bottom is the address of two objects passed in and then the copy constructor is called.





Initialize 5:classtest ct5 = classtest ()





Classtest ct5 = Classtest ();//Copy initialization


010B955C Lea Ecx,[ct5]


010b9562 call Classtest::classtest (0dc12adh)





Tracked down and found it jumped to the default constructor of the class;







Classtest ()


010b4c70 Push EBP


010B4C71 mov Ebp,esp


010b4c73 Sub Esp,0cch


010b4c79 push EBX


010b4c7a push ESI


010b4c7b Push EDI


010b4c7c push ECX


010b4c7d Lea EDI,[EBP-0CCH]


010B4C83 mov ecx,33h


010B4C88 mov eax,0cccccccch


010B4C8D Rep STOs dword ptr Es:[edi]


010b4c8f pop ECX


010B4C90 mov dword ptr [THIS],ECX


010B4C93 mov Eax,dword ptr [this]


010b4c96 mov dword ptr [eax],10bdc70h


{


C[0] = ' the ';


010B4C9C mov eax,1


010B4CA1 Imul ecx,eax,0


010B4CA4 mov Edx,dword ptr [this]


010B4CA7 mov byte ptr [edx+ecx+4],0


cout << "Classtest ()" << Endl;








Say yes to generate a temporary object, and then copy or move the temporary object to the CT5, but it is not. Instead, the Ct5 object address is invoked as an argument to invoke the default constructor, which in turn initializes the CT5.





Initialize 6:classtest CT6 = f1 ()





Classtest CT6 = F1 ();


010b9567 Lea EAX,[CT6]


010B956D push EAX


010b956e Call F1 (0DC14BFH)


010b9573 Add esp,4





This initialization of the underlying implementation is also a more unexpected one. First will already exist in the main function stack CT6 object address stack, at this time according to function call rules, you can know CT6 object address as a F1 argument.








Classtest F1 ()


{


00dc5830 push EBP//Stack frame start


00DC5831 mov Ebp,esp


00dc5833 Sub esp,1d0h


00dc5839 push EBX


00dc583a push ESI


00dc583b Push EDI


00DC583C Lea EDI,[EBP-1D0H]


00dc5842 mov ecx,74h


00dc5847 mov eax,0cccccccch


00DC584C Rep STOs dword ptr Es:[edi]


00DC584E mov eax,dword ptr ds:[00dd0000h]//initialization stack


00dc5853 XOR EAX,EBP


00dc5855 mov dword ptr [Ebp-4],eax


Classtest C;


00dc5858 Lea Ecx,[c]//c value ebp+fffffef4h i.e. ebp-12, stating that C is a local variable within the stack


00dc585e call Classtest::classtest (0dc12adh)//calling default constructor initialization C


return C;


00dc5863 Lea Eax,[c]


00dc5869 push eax//c object address


00DC586A mov ecx,dword ptr [ebp+8]//ct6 object address


00dc586d call Classtest::classtest (0dc14bah)//Invoke copy constructor, initialize CT6


00dc5872 mov eax,dword ptr [ebp+8]//Return CT6 object address


}


00dc5875 push edx


00dc5876 mov ecx,ebp


00dc5878 push EAX


00dc5879 Lea EDX,DS:[0DC58A4H]


00dc587f call @_rtc_checkstackvars@8 (0dc1136h)


00dc5884 pop eax


Omit remaining code








As can be seen from the assembly code above, C is a local variable within the stack, and the default constructor is invoked to initialize C. But is the return C statement in the F1 code returning a temporary object like C? Fact When the call to F1, also passed the address of the CT6 object, in F1 internal to the initialization of C, directly through the C object address and CT6 address call the move constructor, the CT6 was initialized, the last return is the CT6 object address. It can be seen that vs will CT6 initialization work inside the function!





Temporary object: F1 ()





F1 ();


00dc9576 Lea EAX,[EBP-814H]


00dc957c push EAX


00dc957d Call F1 (0DC14BFH)


00dc9582 Add esp,4





A temporary object can be regarded as a nameless variable and an object that exists inside the stack. So, like initializing 6, it's just this time the address of the temporary object is passed in, and the last return is the address of the temporary object, and the move constructor is called before returning.





Temporary object: F2 (CT1)



F2 (CT1);
010f9392 Sub esp,104h//open stack space, generating a temporary object that is exactly 260 bytes (256+4, that is, the total size of the virtual table pointer and the private char array)
010F9398 mov ecx,esp//Take the ESP stack top pointer as the starting address for the temporary object
010f939a Lea Eax,[ct1]//Incoming CT1 object address
010F93A0 push EAX
010F93A1 call Classtest::classtest (010f1078h)
010F93A6 call F2 (010F14BFH)
010f93ab Add esp,104h


As can be seen from the assembly code above, the compiler for a function of a formal parameter type, instead of directly passing in the Ct1 object address, generates a temporary object on the stack and initializes it with a copy constructor, and then calls the F2 function at the address of the temporary object.

Summarize




So fragmented and complex assembly, most people look a little headache, and finally a summary:





(1) What is copy initialization (also known as replication initialization): Copies an existing object to the object being created and, if necessary, a type conversion. Copy initialization occurs in the following situations:





To define a variable by using an assignment operator


Passing an object as an argument to a parameter of an unreferenced type


Returns an object with a function that returns a type that is not a reference type


Initializes an element in an array or a member of an aggregate class with a curly braces list








(2) What is direct initialization: When an object is initialized, the object is given a certain parameter by parentheses, and the compiler is required to use a normal function match to select the constructor that best matches the parameters we provide





(3) in the underlying implementation, it can be seen that the compiler's idea is to be able to use temporary objects without temporary objects. So for these copies to initialize, no temporary objects are generated, copied or moved to the target object, but the corresponding constructors are called directly through the function match.





1 classtest ct2 = "AB"; Equivalent to Classtest ct2 ("AB");


2 classtest ct5 =classtest ("AB"); Equivalent to Classtest ct5 ("AB")





In the following statement, Visual Studio generates an unnamed temporary object (in the stack of the main function), noting that the return value type of the F1 is not a reference, and that the F2 parameter type is not referenced.





1 F1 (); Temporary objects are used to store F1 return values


2 F2 (CT1); The temporary object is used to copy the arguments and pass in the function





The following is the direct pass to the assignment expression to the left object address, and then move the copy of the object, note that the F1 return value type is not referenced, if it is referenced, the copy constructor is invoked.





1 Classtest CT6 = F1 ();








(4) Direct initialization and copy initialization efficiency is basically the same, because at the bottom of the implementation is basically the same, so the copy initialization to direct initialization efficiency is not improved.





(5) When the copy initialization uses the move constructor: When you define a move constructor, the following conditions call the move constructor





Returns an object with a function that returns a type that is not a reference type








(6) When copying is initialized using the copy constructor:





To the right of an assignment expression is an object


When directly initialized, the arguments in parentheses are an object


Initializes an element in an array or a member of an aggregate class with a curly braces list


Returns an object with a function that returns the type as a reference type


A function that is not a reference type, where a parameter is copied to a temporary object






Explain in depth the difference between direct initialization and replication initialization



Share with you my understanding of direct initialization and replication initialization.

First, the primer in the statement


First, let's take a look at what the classics say:
"When used with class-type objects, the initialization forms differ from the direct form: direct initialization directly invokes the constructor that matches the arguments, and replication initialization always calls the copy constructor. Replication initialization first creates a temporary object with the specified constructor, and then copies the temporary object to the object being created with the copy constructor.
There is another paragraph that says,
"Typically direct initialization and replication initialization differ only on low-level optimizations, however, they are intrinsically different for types that do not support replication, or for use of explicit constructors:
Ifstream file1 ("filename")://ok:direct initialization
Ifstream file2 = "filename";//error:copy constructor is private


Ii. Common Misconceptions




From the above argument, we can see that direct initialization does not necessarily call the copy constructor, and replication initialization must call the copy constructor. However, most people think that direct initialization is to invoke the copy constructor when the object is constructed, and that copying initialization is to invoke the assignment operation function (operator=) When the object is constructed, but this is a big misunderstanding. Because the initialization occurs only when an object is created, the assignment operation is not applied to the creation of the object, and primer does not. The reason for this misunderstanding may be that there is an equal sign (=) in the wording of the copy initialization.





To clarify the problem, or to explain it from the code is easier to understand, see the following code:





#include <iostream>


#include <cstring>


using namespace Std;





Class Classtest


{


Public


Classtest ()


{


C[0] = ' the ';


cout<< "Classtest ()" <<endl;


}


classtest& operator= (const classtest &CT)


{


strcpy (c, ct.c);


cout<< "classtest& operator= (const classtest &ct)" <<endl;


return *this;


}


Classtest (const char *PC)


{


strcpy (c, PC);


cout<< "classtest (const char *pc)" <<endl;


}


Private


Classtest (const classtest& CT)


{


strcpy (c, ct.c);


cout<< "classtest (const classtest& CT)" <<endl;


}


Private


Char c[256];


};





int main ()


{


cout<< "Ct1:";


Classtest ct1 ("AB");//Direct initialization


cout<< "CT2:";


Classtest ct2 = "AB";//Copy initialization


cout<< "CT3:";


Classtest ct3 = ct1;//Copy Initialization


cout<< "Ct4:";


Classtest Ct4 (CT1);//Direct initialization


cout<< "CT5:";


Classtest ct5 = Classtest ();//Copy initialization


return 0;


}





The output results are:


From the results of the output, we can know the structure of the object to call which functions, from Ct1 and Ct2, Ct3 and ct4 comparison, it can be seen that Ct1 and Ct2 object construction calls are the same function--classtest (const char *pc), the same reason, Ct3 and Ct4 are called the same function--classtest (const classtest& CT), and CT5 invokes the default constructor directly.

As a result, many people think that classtest ct1 ("AB") is equivalent to classtest CT2 = "AB", and classtest ct3 = Ct1; it is also equivalent to Classtest Ct4 (CT1), and none of them call the assignment action function. So they're all directly initialized, but is the truth really what you think it is? The answer is obviously not.

Third, the level of advancement, who deceived us in the end




Many times, your own eyes will often deceive yourself, here is an example, it is your eyes deceive you. Why is that? The reasons are also explained in the supplement to the optimization. Just because the compiler will help you do a lot of things you don't see, you don't know the optimization, you see the results, it is the compiler did the optimized code after the operation of the results, is not the real results of your code.





You may not believe what I say, then you can uncomment the line in the Copy function function in the class, let the copy constructor become a private function, and then compile and run the program to see what happens.





Obviously, a compilation error has occurred, and from the results above, you might think that it was because CT3 and Ct4 used the copy constructor--classtest (const classtest& CT) During the build process, and now it becomes a private function that cannot be used outside of the class. So there's a compile error, but you can also annotate the CT3 and CT4 function statements as follows:








int main ()


{


cout<< "Ct1:";


Classtest ct1 ("AB");


cout<< "CT2:";


Classtest ct2 = "AB";


cout<< "CT3:";


Classtest ct3 = Ct1;


cout<< "Ct4:";


Classtest Ct4 (CT1);


cout<< "CT5:";


Classtest ct5 = Classtest ();


return 0;


}





However, you are still very sorry to find that there is no compilation passed. What is this for? From the above statement and the previous run results, it is true that the copy constructor has not been invoked, why is it a compile error?





After experimentation, the main function can only be compiled by:








int main ()


{


cout<< "Ct1:";


Classtest ct1 ("AB");


return 0;


}





Here we can see that the original copy constructor deceives us.





Iv. uncovering the Truth




See here, you may have been frightened, below let me to uncover the truth!





Or that sentence, what is direct initialization, and what is replication initialization?





In simple terms, the definition of an object is not the same, one with parentheses, such as Classtest ct1 ("AB"), and one with an equal sign, such as classtest ct2 = "AB".





But in essence, they are fundamentally different: direct initialization directly invokes the constructor that matches the arguments, and replication initialization always calls the copy constructor. Replication initialization first creates a temporary object using the specified constructor, and then copies the temporary object to the object being created with the copy constructor. So when the copy constructor is declared private, all replication initialization is not available.





Now let's look back at the statement in the main function,


1, Classtest ct1 ("AB"); This statement belongs to direct initialization, it does not need to call the copy constructor, calls the constructor classtest (const char *pc) directly, so it can be executed directly when the copy constructor becomes private.





2, Classtest ct2 = "AB"; This statement initializes the copy, which first calls the constructor classtest (const char *pc) function to create a temporary object and then calls the copy constructor, which takes the temporary object as a parameter, constructs the object ct2 ; therefore, when the copy constructor becomes private, the statement cannot be compiled.





3, Classtest ct3 = CT1; This statement initializes the copy because Ct1 already exists, so you do not need to invoke the associated constructor, call the copy constructor directly, and copy the value to the object ct3, so when the copy constructor becomes private, the statement cannot be compiled.





4, Classtest Ct4 (CT1); This statement is for direct initialization because CT1 already exists, call the copy constructor directly, and generate the replica object ct3 of the object Ct4. So when the copy constructor becomes private, the statement cannot be compiled.





Note: The 4th object CT4 is the same as the function called for the creation of the 3rd object Ct3, but I think the reason for calling the copy function is different. Because direct initialization invokes a constructor based on a parameter, such as Classtest Ct4 (CT1), it is directly determined to invoke the copy constructor classtest (const classtest& CT), based on the arguments in parentheses (an object of this class). When this is overloaded with functions, it is true that the corresponding function is invoked based on the parameters of the function call, and for CT3, it is not called as CT4, it is based on parameters to determine the copy constructor to invoke, simply because initialization necessarily calls the copy constructor. It is supposed to create a temporary object, but only this object already exists, so omit this step, and then call the copy constructor directly, because copy initialization is bound to invoke the copy constructor, so the creation of CT3 is still a replication initialization.





5, Classtest ct5 = Classtest (); This statement initializes the copy, starts by calling the default constructor to produce a temporary object, and then calls the copy constructor, which takes the temporary object as a parameter and constructs the object ct5. So when the copy constructor becomes private, the statement cannot be compiled.








v. The cause of false appearance

The main reason that


produces the results above is compiler optimization, and why is declaring a copy constructor private (private) can remove this illusion? The main reason is that the copy constructor can be synthesized by default and is public, and the compiler optimizes the code based on this attribute. However, if you define the copy constructor yourself, the compilation will not be generated automatically, although the compilation will not be generated automatically, but if you define the copy constructor is still public, the compilation will do the same optimization for you. However, when it is a private member, the compiler has a very different behavior because you explicitly tell the compiler that you explicitly reject the copy operation between objects, so it doesn't help you do the optimizations you've done before, and your code comes out of the way.

For example, just like the following statement:
classtest ct2 = "AB";
It was intended to construct the object by first calling the constructor classtest (const char *pc) function to create a temporary object and then calling the copy constructor, which takes the temporary object as a parameter and constructs the object ct2. However, the compilation also finds that the copy constructor is public, that you explicitly tell the compiler that you allow replication between objects, and that at this point it finds that the object can be initialized directly by calling the overloaded constructor classtest (const char *pc) directly, and the same effect is achieved. So the statement is optimized to Classtest ct2 ("AB").

And if the copy constructor is declared private, the copy before the object cannot be used, that is, the copy constructor cannot be invoked as a parameter, so the compilation considers that classtest CT2 = "AB" and Classtest ct2 ("AB") are not equivalent, Will not help you do this optimization, so the compilation error.

Note: According to the code above, some people might run a different result than the one I tested, why? As I said before, the compiler will make some optimizations for your code, but different compilers may have different optimizations, so when you're using a different compiler, because these optimizations don't work the same way, I'm using a g++4.7.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.