C++, constructors and copy constructors

1. Initialization constructor

The function body of the constructor belongs to the assignment of data members. Initialization occurs before the function body statement, so members that need to be explicitly initialized must be initialized in the initialization list, including:

  1. Contains const or reference members and does not provide a default initialization value, which can only be initialized but not assigned.
  2. Contains class data members without a default constructor. Those without a default constructor cannot be initialized implicitly. The compiler automatically generates a default constructor only when a class does not define any constructor.
  3. The base class does not have a default constructor, and the derived class must explicitly call the base class's constructor using an initialization list. This is similar to item 2.

The order in which members are initialized in the initialization list is consistent with the order in which they are declared in the class, regardless of the order in the initialization list. Do not initialize members declared earlier with members declared later. However, it is an assignment operation inside the constructor, and values ​​cannot be assigned in the order of declaration.

It is best to use default arguments instead of the default constructor, such asA(int _a=0) { a = _a; }, Can save code amount.

A derived class cannot directly initialize data members of a base class, even if they are visible to the derived class. However, derived classes can assign values ​​to visible members of the base class within the constructor.

2. Copy constructor and overloaded assignment operator

The compiler will provide a default copy constructor and overloaded assignment operator, but this is only a shallow copy, that is, simply copying the value of its data member. If the data member contains a pointer and points to memory other than the object, it is easy to cause an access conflict. Case. In order to handle object copying more flexibly, it is usually necessary to manually define the copy constructor and overloaded assignment operator. The general form is as follows:

 A(const A& a);
 A& operator=(const A& a);

Note that for copy constructors, input parameters must be passed by reference. This is not a matter of coding style, but a requirement of the C++ standard. This is because passing by value will lead to recursive calls to the copy constructor, because passing by value itself requires calling the copy constructor. As for the assignment operator, passing by value only calls the copy constructor one more time, so this is allowed, but in terms of efficiency, references are usually used.

Note that although sometimes input parameters are not forced to be const, not using const modification will cause a lot of trouble. Parameters without const modification can only accept lvalue input, but cannot accept rvalues ​​(i.e. unnamed temporary variables). Of course, after C++11 we can define move constructors through rvalue references to deal with this problem. But a more serious problem is that the input of the copy constructor is sometimes a const object itself. If the parameters are not const modified, the parameter list cannot match, and the copy constructor cannot be called. For example, in the vector container, we can create a length n and initial value x by calling vector(size_t n, const T& x); vector, but if the copy constructor parameter of T is not const, we will not be able to copy x, which will seriously affect our use of STL containers.

Overloading of the assignment operator does not necessarily have a return value, because when overloaded, the function will contain the this pointer of the left operand, so that its value can be modified directly. The main purpose of using the return value is to realize the continuous assignment of the equal sign, that is, for example, x=y=z; But note that reference return is generally used at this time, and the reference of *this is returned. Because the scope of the *this object is outside the function, it makes sense to return a reference to it. If the *this object is returned directly, then for x=y; in addition to calling operator= once, there is also a call to the copy constructor, that is, the compiler generates a temporary variable to store the result returned by operator=. For x=y=z; is actually equivalent to A tmp1(y=z); A tmp2(x=tmp1); that is, the copy constructor is called twice more, so you should try to use reference return.

3. Move copy constructor and move assignment function (C++11)

For copy constructors and assignment overloading, we use const to prevent modifications to the original object. Therefore, when A contains dynamically allocated memory, a new memory copy should be created to copy the contents of the original object instead of directly copying it. Memory address, which is a deep copy of the object. If you copy the address of dynamic memory directly, when the original object is destructed at a certain stage, the memory will be released by delete. At this time, it is meaningless to retain its pointer. This is a shallow copy and multiple pointers pointing to the same block. Disadvantages of memory.

However, if the original object is an rvalue, that is, some temporary variables, we do not need to create a new memory copy, because we know that the temporary variables will be destroyed immediately even if they are not modified. At this time, we directly point to the dynamic memory that the original object points to. The address can be retained, but the pointer of the original object needs to be set to null so that the memory will not be released when the original object is destructed. This is the meaning of the move constructor. Suppose A contains a pointer such as int *ptr, its general form is as follows:

    A(A&& a) noexcept    // 移动构造以及移动赋值函数一般要加上 noexcept,
    {
    
                        // 用于告诉编译器函数不会发生异常,可以进行优化,没有的话STL可能调用的是普通拷贝构造函数
        ptr = a.ptr;
        a.ptr = nullptr;
    }

Because we need to modify the pointer of the original object, we cannot use const modification. At this time, the compiler will choose the copy constructor or the move constructor based on whether the original object is an lvalue or an rvalue.

 A getA() 
 {
    
    
    A x;
    return x;
 }
 A a = getA();

In the above example, one construction and two copies actually occur, that is, the local variable x is constructed first, and x will be destructed and destroyed after the function ends, and is a temporary variable. Because a copy occurs when returning by value, another temporary variable getA() is generated, which is finally copied to the named object a. If A contains dynamically allocated memory, the ordinary copy constructor needs to copy the dynamic memory twice due to deep copy, while the move constructor does not require any dynamic memory copy, which reduces the complexity of program running. In fact, the above examples are usually optimized in the compiler to require only a single construction without copying. To turn off optimization, use -fno-elide-constructors Options.

Guess you like

Origin blog.csdn.net/qq_33552519/article/details/124046299