"C++ Programming Thought" says that if a class does not have a copy function, the compiler will automatically create a default copy function. Let's take a look at the real situation below.
First, let’s look at a simple class X, which does not display the definition copy constructor.
The source code of c++ is as follows:
class X {
private:
int i;
int j;
};
int main() {
X x1;//Define object x1 first
X x2 = x1;//Copy x1 to x2
}
Here is its assembly code:
_main PROC
; 7 : int main() {
push ebp
mov ebp, esp
sub esp, 16 �
; 8 : X x1;//Define object x1 first
; 9 : X x2 = x1;//Copy x1 to x2
mov eax, DWORD PTR _x1$[ebp]; to register eax, then to the value of member variable i in x1 to eax
mov DWORD PTR _x2$[ebp], eax; Write the value in eax to the first address of x2, that is, write the value in eax to the member variable i of x2
mov ecx, DWORD PTR _x1$[ebp+4]; offset the value in memory of the first address of x1 4byte to register eax, that is, the value of member variable j in x1 to ecx
mov DWORD PTR _x2$[ebp+4], ecx; Write the value in ecx into the memory of offset x2 address 4byte, that is, write the value in ecx to the member variable j of x2
; 10 : }
xor eax, eax
mov esp, ebp
pop ebp
ret 0
_main ENDP
As can be seen from the assembly code, no function is called at all, and all copy assignments are completed through the communication between registers and memory addresses. Just like the compiler provides a default constructor, this situation can be considered as the compiler provides a useless copy constructor.
So, when will the compiler really provide the default copy constructor and display the call?
The following is a case where class X contains virtual member functions:
C++ source code:
class X {
private:
int i;
int j;
public:
virtual ~X() {}//virtual destructor
};
int main() {
X x1;//Define object x1 first
X x2 = x1;//Copy x1 to x2
}
Since only copy functions are discussed here, only look at the assembly code in the main function main and copy functions:
The following is the assembly code in the main function main:
_main PROC
; 9 : int main() {
push ebp
mov ebp, esp
sub esp, 24 �
; 10 : X x1;//Define object x1 first
lea ecx, DWORD PTR _x1$[ebp]; Get the first address of x1, put ecx, and pass it in for the secret parameter of the call constructor, that is, this
call ??0X@@QAE@XZ;Call the constructor
; 11 : X x2 = x1;//Copy x1 to x2
lea eax, DWORD PTR _x1$[ebp]; Get the first address of x1 and put it in register eax
push eax; press eax as a parameter to the copy constructor
lea ecx, DWORD PTR _x2$[ebp]; Get the first address of x2, put it in the register ecx, and pass it in as a secret parameter to call the copy constructor, that is, this
call ??0X@@QAE@ABV0@@Z;Call the copy constructor
; 12 : }
lea ecx, DWORD PTR _x2$[ebp]; Get the first address of x2, put it into the ecx register, as a secret parameter passed in by calling the destructor, that is, this
call ??1X@@UAE@XZ �
lea ecx, DWORD PTR _x1$[ebp]; Get the first address of x1, put it into the ecx register, as a secret parameter passed in by calling the destructor, that is, this
;The order of destruction is the opposite of the order of construction
call ??1X@@UAE@XZ �
xor eax, eax
mov esp, ebp
pop ebp
ret 0
_main ENDP
As you can see, the compiler provides the default copy constructor for class X (non-useless default copy constructor) and displays the call.
Since a class inherits from a virtual base class or a base class with virtual function members, it also contains virtual function members, it is also in the previous situation. So in this case, the compiler will also provide a non-useless default copy constructor and be able to display the call.
The following is the second case. Class X inherits from class Y. Class Y has a copy constructor that displays the definition, while the class does not provide a copy constructor:
Here is the c++ source code:
class Y {
private:
int j;
public:
Y(const Y& y) {}
Y() {};//The default constructor must be provided for Y, otherwise there will be an error in compilation
};
class X : public Y {
private:
int i;
int j;
};
int main() {
X x1;//Define object x1 first
X x2 = x1;//Copy x1 to x2
}
The following is the assembly code of the mian function:
; 16 : int main() {
push ebp
mov ebp, esp
sub esp, 24 �
; 17 : X x1;//Define object x1 first
lea ecx, DWORD PTR _x1$[ebp]; Get the first address of x1 and pass it to the constructor as an implicit parameter
call ??0X@@QAE@XZ;//Calling the default constructor provided by the compiler for class X
; 18 : X x2 = x1;//Copy x1 to x2
lea eax, DWORD PTR _x1$[ebp]; Get the first address of x1 and pass it to register eax
push eax; press eax as a parameter to call the copy constructor of class X
lea ecx, DWORD PTR _x2$[ebp]; Get the first address of x2 as an implicit parameter for calling copy function of class X
call ??0X@@QAE@ABV0@@Z; Call the default copy constructor provided by the compiler
; 19 : }
xor eax, eax
mov esp, ebp
pop ebp
ret 0
Here is the assembly code of the copy constructor of class X:
??0X@@QAE@ABV0@@Z PROC ; X::X, COMDAT
; _this$ = ecx
push ebp
mov ebp, esp
push ecx
mov DWORD PTR _this$[ebp], ecx; ecx has the first address of x2 in
mov eax, DWORD PTR ___that$[ebp]; Give the first address of x1 to eax
push eax; Press the first address of eax as a parameter to call the parent class copy constructor
mov ecx, DWORD PTR _this$[ebp]; transfer the first address of x2 to ecx and pass it as an implicit parameter to the copy constructor of the parent class
call ??0Y@@QAE@ABV0@@Z
mov ecx, DWORD PTR _this$[ebp]; Get the first address of x2 to ecx
mov edx, DWORD PTR ___that$[ebp]; Get the first address of x1 to edx
mov eax, DWORD PTR [edx+4]; Write the value in memory at the offset x1's first address 4byte to eax, that is, write the value of the subclass member variable i in x1 to eax, because the parent class member variable i is stored at the first address of x1, and its value
;The parent class copy constructor is responsible for copying to x2
mov DWORD PTR [ecx+4], eax; Write the value of eax into the memory deviating from the first address of x2 4byte, that is, write the value of eax to the subclass member variable i in x2, because the parent class member variable i is stored at the first address of x2, and its value
;The parent class copy constructor is responsible for copying
mov ecx, DWORD PTR _this$[ebp]; Give the first address of x2 to ecx
mov edx, DWORD PTR ___that$[ebp]; Give the first address of x1 to edx
mov eax, DWORD PTR [edx+8]; to eax the value in memory at the first address of x1 at 8byte, that is, to eax the value of the subclass member variable j in x1
mov DWORD PTR [ecx+8], eax; Write the value of eax into the memory of the offset x2 head address 8byte, that is, write the value of eax to the member j of the subclass x2
mov eax, DWORD PTR _this$[ebp];Put the first address of x2 to eax as the return value. The constructor always returns the object's first address
mov esp, ebp
pop ebp
ret 4
??0X@@QAE@ABV0@@Z ENDP
As can be seen from the assembly code, the compiler does provide the default copy constructor for class X and makes display calls. Moreover, in calling the copy constructor of class X, first call the copy constructor of the parent class, copy the member variables in the parent class, and then copy the member variables in the subclass.
The following is the copy constructor assembly code in parent class Y:
??0Y@@QAE@ABV0@@Z PROC ; Y::Y, COMDAT
; _this$ = ecx
; 5 : Y(const Y& y) {}
push ebp
mov ebp, esp
push ecx;//The purpose of pushing the stack here is to implicitly pass this to the parent class copy function (that is, the first address of x2)
mov DWORD PTR _this$[ebp], ecx;ecx contains the first address of x2 (i.e. this), and put it in the reserved space just now
mov eax, DWORD PTR _this$[ebp]; Write the first address of x2 to eax as the return value. The constructor always returns the object's first address
mov esp, ebp
pop ebp
ret 4
??0Y@@QAE@ABV0@@Z ENDP ; Y::Y
_TEXT ENDS
From assembly, we can see that since the parent class itself shows that the copy constructor is defined, the compiler is only responsible for calling it, and does not provide the copy function of the default copy constructor in subclass X above, that is, it does not copy the parent class member variable i. Because, in the c++ source code, the parent class copy constructor itself is an empty function and does nothing.
What happens sometimes if subclass X and parent class Y do not provide copy constructors?
The following is the c++ source code:
class Y {
private:
int j;
};
class X : public Y {
private:
int i;
int j;
};
int main() {
X x1;//Define object x1 first
X x2 = x1;//Copy x1 to x2
}
The following is the corresponding assembly code:
_main PROC
; 12 : int main() {
push ebp
mov ebp, esp
sub esp, 24 �
; 13 : X x1;//Define object x1 first
; 14 : X x2 = x1;//Copy x1 to x2
mov eax, DWORD PTR _x1$[ebp]; Get the value in the first address of x1, store it in eax, that is, get the value of the member variable i of the parent class x1 class i and write it to eax
mov DWORD PTR _x2$[ebp], eax; Write the value of eax to the memory pointed to by the first address of x2, that is, write the value of eax to the parent class member variable i in x2
mov ecx, DWORD PTR _x1$[ebp+4]; Get the value in memory at the offset x1 first address 4byte, write to ecx, that is, get the value of the x1 subclass member variable i and write to ecx
mov DWORD PTR _x2$[ebp+4], ecx; Write the value of ecx into the memory at the first address of x2 4byte, that is, write the value of ecx to the subclass member variable i in x2
mov edx, DWORD PTR _x1$[ebp+8]; Get the value in memory at the offset x1 first address 8byte, write to edx, that is, get the value of x1 subclass member variable j and write to edx
mov DWORD PTR _x2$[ebp+8], edx; Write the value of edx into the memory at the offset x2 head address 8byte, that is, write the value of edx to the x2 subclass member variable j
; 15 : }
xor eax, eax
mov esp, ebp
pop ebp
ret 0
_main ENDP
As you can see, the compiler executes the copy process, but provides a useless default copy constructor like the beginning. Whether it is copying the parent class member variable or the child class member variable, there is no call to the function.
Let’s look at the third case. Class X contains member variables of class Y, and member variables of class Y have copy constructors.
The source code of c++ is as follows:
class Y {
private:
int j;
public:
Y(const Y& y) {}
Y() {}//The default constructor must be provided for Y, otherwise the compilation error will be reported.
};
class X {
private:
int i;
int j;
Y y;
};
int main() {
X x1;//Define object x1 first
X x2 = x1;//Copy x1 to x2
}
The following is the assembly code in the main function:
_main PROC
; 16 : int main() {
push ebp
mov ebp, esp
sub esp, 24 �
; 17 : X x1;//Define object x1 first
lea ecx, DWORD PTR _x1$[ebp]; Get the first address of x1 and pass it to the constructor as an implicit parameter
call ??0X@@QAE@XZ;Call the constructor
; 18 : X x2 = x1;//Copy x1 to x2
lea eax, DWORD PTR _x1$[ebp]; Get the first address of x1 and put it in register eax
push eax; press the eax as a stack, and pass it as a parameter to the default copy constructor provided by the compiler
lea ecx, DWORD PTR _x2$[ebp]; Get the first address of x2 and pass it as an implicit parameter to the default copy constructor provided by the compiler
call ??0X@@QAE@ABV0@@Z;Call the copy constructor
; 19 : }
xor eax, eax
mov esp, ebp
pop ebp
ret 0
_main ENDP
Here is the assembly code of the default copy constructor provided by the compiler for class X:
??0X@@QAE@ABV0@@Z PROC ; X::X, COMDAT
; _this$ = ecx
push ebp
mov ebp, esp
push ecx; The purpose of pushing the stack is to reserve space for this (i.e. the first address of x2)
mov DWORD PTR _this$[ebp], ecx;ecx contains the first address of x2 and put it in the space just reserved
mov eax, DWORD PTR _this$[ebp]; Give the first address of x2 to eax
mov ecx, DWORD PTR ___that$[ebp]; Give the first address of x1 to ecx
mov edx, DWORD PTR [ecx]; Write the content of the first address of x1 to edx, that is, write the member variable i in x1 to edx
mov DWORD PTR [eax], edx; Write the value of edx to the first address of x2, that is, write the value of edx to the member variable i of x2
mov eax, DWORD PTR _this$[ebp]; Write the first address of x2 to register eax
mov ecx, DWORD PTR ___that$[ebp]; Write the first address of x1 to register ecx
mov edx, DWORD PTR [ecx+4]; Write the value in memory at the first address of x1 offset x1 to edx, that is, write the value of x1 member variable j to edx
mov DWORD PTR [eax+4], edx; Write the value of edx to the memory at the first address of x2 address 4byte, that is, write the value of edx to the member variable j of x2
mov eax, DWORD PTR ___that$[ebp]; Store the first address of x1 into register eax
add eax, 8;//Add 8 the first address of x1 to get the address where the member object y is located in x1 and put it in eax
push eax; Press the value of eax as a parameter to call the copy function of the member variable y
mov ecx, DWORD PTR _this$[ebp]; Store the first address of x2 into register ecx
add ecx, 8; add 8 to the first address of x2 to get the address where the member object y is located in x2, and put it in ecx. This address is passed as an implicit parameter to the copy constructor of the member variable function
call ??0Y@@QAE@ABV0@@Z
mov eax, DWORD PTR _this$[ebp]; Put the first address of x2 into eax as the return value. The constructor always returns the object's first address
mov esp, ebp
pop ebp
ret 4
??0X@@QAE@ABV0@@Z ENDP
From assembly, we can see that when calling class X copy constructor, first copy the member variables i and j in x1 into x2, and then call the copy constructor of member object y to copy the member variables in y. This is different from inheritance. In inheritance, the copy constructor of the parent class is always called first, and then copying in the subclass. This shows that for this case where member objects are included, the timing of copy function calls of member objects is related to the location they define. Here, the member object y of class X is defined after the member variables i and j. Therefore, its copy constructor will not be called after copying i and j.
The following is the copy constructor assembly code in class Y:
??0Y@@QAE@ABV0@@Z PROC ; Y::Y, COMDAT
; _this$ = ecx
; 6 : Y(const Y& y) {}
push ebp
mov ebp, esp
push ecx; The purpose of pushing the stack ecx is to store this (the first address of member object y in x2) to reserve space
mov DWORD PTR _this$[ebp], ecx; ecx has the first address of the member object y in x2, and put it in the reserved space just now
mov eax, DWORD PTR _this$[ebp]; Put the first address of the member variable in x2 into eax as the return value. The constructor always returns the object's first address
mov esp, ebp
pop ebp
ret 4
??0Y@@QAE@ABV0@@Z ENDP
As can be seen from the code, since the class Y display defines the copy constructor, the compiler is only responsible for displaying the calls and does not provide any copy function. Because in class Y, the copy constructor is defined as an empty function
Like inheritance, what if the member object does not have a copy constructor?
The following is the c++ source code:
class Y {
private:
int j;
};
class X {
private:
int i;
int j;
Y y;
};
int main() {
X x1;//Define object x1 first
X x2 = x1;//Copy x1 to x2
}
Here is the assembly code of the object:
_main PROC
; 14 : int main() {
push ebp
mov ebp, esp
sub esp, 24 ; 00000018H
; 15 : X x1;//Define object x1 first
; 16 : X x2 = x1;//Copy x1 to x2
mov eax, DWORD PTR _x1$[ebp]; Write the content of the first address in x1 to eax, that is, write the member variable value i in x1 to eax
mov DWORD PTR _x2$[ebp], eax; Write the value of eax to the first address of x2, that is, write the value of eax to the member variable i of x2
mov ecx, DWORD PTR _x1$[ebp+4]; Write the contents in memory at the offset x1 address 4byte to ecx, that is, write the value of member variable j in x1 to ecx
mov DWORD PTR _x2$[ebp+4], ecx; Write the value of ecx to the memory at the first address of x2 4byte, that is, write the value of ecx to the member variable j in x2
mov edx, DWORD PTR _x1$[ebp+8]; Write the memory value at offset x1's first address 8byte (here is the first address of x1 member object y) to edx, that is, write the member variable i value in member object y in x1 to edx
mov DWORD PTR _x2$[ebp+8], edx; Write the value of edx into the memory at the offset x2 head address 8byte (here is the first address of x2 member object y), that is, write the value of edx into the member variable i of member object y in x2
From assembly, we can see that in this case, the compiler only provides a useless default copy constructor, that is, no function calls are displayed, but only uses the communication between registers and memory to complete the copy process.
Based on the above analysis, we can see:
For a class, if it does not display a defined copy constructor, the compiler does not always provide a non-useful default copy constructor unless:
1 This class contains virtual function member functions (including virtual function members inherited from virtual base classes or inherited base classes). At this time, the compiler provides a non-useless default copy constructor for the class.
2 This class inherits from a base class, which contains a custom copy function. At this time, the compiler provides the class with a non-useful default copy constructor. (If the base class itself does not define a copy constructor, but the compiler will provide a non-useless default copy constructor for the base class, which is also the case. That is to say, the base class only needs to contain a non-useless copy constructor, regardless of whether the non-useless copy constructor is custom or provided by the compiler)
3 This class contains a member object, and the member object has a custom copy constructor. At this time, the compiler provides the class with a non-useful default copy constructor. (If the member object itself does not define a copy constructor, but the compiler will provide a non-useless default copy constructor for the member object, which is also the case. That is, the member object only needs to include a non-useless copy constructor, regardless of whether it is customized when the non-useless copy constructor or provided by the compiler. This situation is similar to the previous one).
Moreover, if a class customizes a copy constructor, the compiler is only responsible for calling and will not provide any additional copying procedures; and for the default copy functions provided by the compiler, whether useless or non-useless, they are just bit copying (i.e. shallow copy).