This is a question about the principle of temporary copying of the member variable in C++ Default Copy Generator

Asked 2 years ago, Updated 2 years ago, 98 views

class baseDMA
{
private:
    char* label;
    int rating;
public:
    baseDMA(const char* l="NULL", int r =0);
    baseDMA(const baseDMA& rs);
    virtual ~baseDMA();
    baseDMA& operator=(const baseDMA& rs);
    friend ostream& operator<<(ostream& os, const baseDMA& rs);
};

//Derivation classes without DMA
I don't need a destroyer
//Use an implicit copy generator
Use an implicit substitution operator
class lacksDMA : public baseDMA
{
private:
    enum { COL_MEN = 40 };
    char color[COL_MEN];
public:
    lacksDMA(const char* c="blank", const char*l="NULL", int r = 0);
    lacksDMA(const char* c, const baseDMA& rs);
    friend ostream& operator<<(ostream& os,const lacksDMA& ls);
};
int main()
{
    lacksDMA balloon2 (balloon); // Assume ballon is a lacksDMA object, predefined, initialized

    return 0;
}

Hi, everyone I have no way of knowing, so I'm asking because I'm looking here and there and I don't get an answer, so I think it's the last time.

It's about copying between members when calling the default copy generator. If the member is an array, I would like to know how it is copied.

The above source describes the definition of a copy generator, a large operator, and a destroyer depending on whether or not dynamic memory placement is used in the derived class.

Because the source does not use dynamic memory in the derived class, the derived class only needs the default copy generator, the default copy generator, and the default substitution operator.That means you don't have to define these three things separately

But the problem is that the default copy generator is curious.If you look at the main function, The racksDMA object balloon is initializing balloo2.

Here, since lacksDMA does not require a copy generator, for initialization of balloo2, The balloon object is used and therefore the default copy generator is called.

The copy creator learned that copies are made between members.But if the member variable is an array, I wonder how the copy is made.

Simple inta = b; It would be nice if it was this simple substitution, but wouldn't it work in an array? For example,

char a[10] = "School";
char b[10];
b = a; //Error

Isn't this the wrong code, for example, the source? To copy from this char array, use the strcpy() or strncpy() function, or use the iterations You should use a method such as substituting or etc.

Likewise, how is the member variable copied in the stacksDMA? Is it possible to copy internally by using strcpy or strncpy functions?

I thought copying by member was a concept of copying due to simple college entrance, but now that I think about the arrangement, I think this is not right. Please. I'm so curious.

c++ visual-studio-2010 constructor array

2022-09-22 20:16

1 Answers

The process differs depending on the compiler, depending on whether the member variable is a POD type or a class type with a copy generator.

Also, auto-generated copy constructors may not be represented in the C++ language. These are clearly visible in assembly language and can be helpful for understanding.

First, let's express the automatically generated copy constructor directly in C++ and then show the assembly code.

For the POD type, copy via memcpy() similar to strcpy(). In other words, in the case of the code you mentioned, you can think that the copy generator below is automatically created.

lacksDMA::lacksDMA(lacksDMA const& rhs)
    : : baseDMA(rhs)
{
    std::memcpy(color, rhs.color, sizeof(color));
}

Let's look at the assembly code to make sure what I said is correct.

The following conversion to the code assembly code you created is located at https://godbolt.org/g/V6UpcV. If you look at the assembly code, you can see the following:

lacksDMA::lacksDMA(lacksDMA const&):
  push rbp
  mov rbp, rsp
  sub rsp, 16
  mov QWORD PTR [rbp-8], rdi
  mov QWORD PTR [rbp-16], rsi
  mov rax, QWORD PTR [rbp-8]
  mov rdx, QWORD PTR [rbp-16]
  mov rsi, rdx
  mov rdi, rax
  call baseDMA::baseDMA(baseDMA const&)
  mov edx, OFFSET FLAT:vtable for lacksDMA+16
  mov rax, QWORD PTR [rbp-8]
  mov QWORD PTR [rax], rdx
  mov rcx, QWORD PTR [rbp-8]
  mov rsi, QWORD PTR [rbp-16]
  mov rax, QWORD PTR [rsi+20]
  mov rdx, QWORD PTR [rsi+28]
  mov QWORD PTR [rcx+20], rax
  mov QWORD PTR [rcx+28], rdx
  mov rax, QWORD PTR [rsi+36]
  mov rdx, QWORD PTR [rsi+44]
  mov QWORD PTR [rcx+36], rax
  mov QWORD PTR [rcx+44], rdx
  mov rax, QWORD PTR [rsi+52]
  mov QWORD PTR [rcx+52], rax
  nop
  leave
  ret

As you can see, there is a part called lacksDMA:lacksDMA (lacksDMA const&): which is the copy generator that the compiler automatically generated. The content below that becomes the content of the corresponding copy creator.

There is call baseDMA::baseDMA (baseDMA const&) in the content, where you call the copy constructor of the parent class baseDMA.

The code below will then be the assembly that copies the array.

 movrax, QWORD PTR [rsi+20];// read balloon.color[0] to balloon.color[7]
  movedx, QWORD PTR [rsi+28];// balloon.color[8] ~ balloon.color[15] Read
  mov QWORD PTR [rcx+20], rx;// Write the read value to balloon2.color[0] ~ balloon2.color[7]
  mov QWORD PTR [rcx+28], rdx;// Write the read value to balloon2.color[8] ~ balloon2.color[15]
  moverax, QWORD PTR [rsi+36];// read balloon.color[16] to balloon.color[23]
  movedx, QWORD PTR [rsi+44];// read balloon.color[24] to balloon.color[31]
  mov QWORD PTR [rcx+36], rx;// Write the read value to balloon2.color[16] ~ balloon2.color[23]
  mov QWORD PTR [rcx+44], rdx;// Write the read value to balloon2.color[24] to balloon2.color[31]
  moverax, QWORD PTR [rsi+52];// read balloon.color[32] to balloon.color[39]
  mov QWORD PTR [rcx+52], rx;// Write the read value to balloon2.color[32] to balloon2.color[39]

In this assembly, the array is read in 8 bytes (QWORD) and copied over a total of 5 times. The reason why it is copied in QWORD units rather than in 1-byte units is that it performs better.

At first, I told you that I would call memcpy(), but as you can see, the copy command was made five times. This is because the compiler determined that it would be faster to copy five times in a row than to invoke memcpy().

So in what situation can we call memcpy()? If you increase the color array size to 512, the following results are obtained.

  mov eax, eax
  mov rsi, rdx
  mov rcx, rax
  rep movsq

https://godbolt.org/g/dPHtFn If you look here, you can see that memcpy() is converted as follows, indicating that a code similar to that of changing color to 512 above.

  mov ecx, 512
  mov rdi, rax
  mov rsi, rdx
  rep movsq

This means that the amount of memory you're going to copy will determine whether to write memcpy().

For POD, memcpy() is used because you can copy member variables simply by copying memory. However, for class types, there is a copy generator that the programmer can create himself, and the way the copy is copied depends on what you create.

Therefore, when automatically generated copy constructors copy a member variable with an array of class types, they call the copy constructors of each array element to proceed with the copy creation.

I will explain based on the code below.

#include <string>

class Array
{
public:
    std::string elements[100];
};

int main()
{
    Array a1;
    Array a2(a1);
    return 0;
}

Because elements is an array with class-type elements, a copy generator using memcpy() like POD is not created, and a code is automatically generated to copy each element as shown below.

Array::Array(Array const& rhs)
{
    // Elements are allocated only memory space and the default constructor is not called home
    for (std::size_t i = 0; i < 100; ++i)
    {
        new (&elements[i]) std::string(rhs.elements[i]);
    }
}

At this time, elements is not created by calling the default constructor, and only memory space is allocated. The auto-generated constructor circulates the loop and calls the copy constructor of the element, such as new (&elements[i]) std:string(rhs.elements[i]);.

If you look at the assembly code (https://godbolt.org/g/WQDCEw), you will find the following:

Array::Array(Array const&):
  push rbp
  mov rbp, rsp
  push r14
  push r13
  push r12
  push rbx
  sub rsp, 16
  mov QWORD PTR [rbp-40], rdi
  mov QWORD PTR [rbp-48], rsi
  mov rbx, QWORD PTR [rbp-40]
  mov r12d, 99
  mov r13, QWORD PTR [rbp-48]
  mov r14, rbx
.L10:
  test r12, r12
  js .L15
  mov rsi, r13
  mov rdi, r14
  call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
  add r14, 32
  add r13, 32
  sub r12, 1
  jmp .L10
  mov r13, rax
  test rbx, rbx
  je .L12
  mov eax, 99
  sub rax, r12
  sal rax, 5
  lea r12, [rbx+rax]
.L13:
  cmp r12, rbx
  je .L12
  sub r12, 32
  mov rdi, r12
  call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()
  jmp .L13
.L12:
  mov rax, r13
  mov rdi, rax
  call _Unwind_Resume
.L15:
  nop
  add rsp, 16
  pop rbx
  pop r12
  pop r13
  pop r14
  pop rbp
  ret

Here, the code below goes around the loop and calls the copy generator of each array element.

.L10:
  test r12, r12
  js .L15
  mov rsi, r13
  mov rdi, r14
  call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
  add r14, 32
  add r13, 32
  sub r12, 1
  jmp .L10

testr12, r12 and js.L15 to check if it has arrived at the end of the loop (whether the last element has been created or not), and if it has arrived at the end, .You will jump with the L15 label. If you still have elements of the array to be created, call std:_cxx11:::basic_string<char, std::char_traces<char>, std:allocator<char> >::basic_string(st:__cxx11;character>::character>::character>:::character>character>::character>character>::::character>character>:::character> Repeat the loop by jumping to the location where the testr12, r12 code is located via jmp.L10.

The automatically generated copy constructors differ in content from compiler to compiler and depend on the definition of the class.

Generally speaking, an array with a POD type as an element copies the array via memcpy() and an array with a class type as an element circulates a loop and invokes a copy constructor for each element.


2022-09-22 20:16

If you have any answers or tips


© 2024 OneMinuteCode. All rights reserved.