SoFunction
Updated on 2025-03-10

Detailed explanation of the use of default copy functions in c++

"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:

Copy the codeThe code 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:
Copy the codeThe code is as follows:

_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:

Copy the codeThe code is as follows:

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:

Copy the codeThe code is as follows:

_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:

Copy the codeThe code is as follows:

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:
Copy the codeThe code is as follows:

; 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:
Copy the codeThe code is as follows:

??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:

Copy the codeThe code is as follows:

??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:

Copy the codeThe code is as follows:

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:
Copy the codeThe code is as follows:

_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:

Copy the codeThe code 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:
Copy the codeThe code is as follows:

_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:
Copy the codeThe code is as follows:

??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:

Copy the codeThe code is as follows:

??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:

Copy the codeThe code is as follows:

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:
Copy the codeThe code is as follows:

_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).