Copying, assigning, cleaning and moving C++ objects

Copying, assigning, cleaning and moving objects

MyString class

Assignment and copying of objects

/*Rectangular class*/
class Rectangle {<!-- -->
public:
    int length = 1; //length
    int width = 2; //width
public:
    Rectangle() = default;
    Rectangle(int L, int W) :length{<!-- --> L }, width{<!-- --> W } {<!-- -->};
    void Print(void) const
    {<!-- -->
        cout << "length: " << length <<','
        << "width: " << width << endl;
    }
};

By default, objects can be copied.
In particular, a class object can be initialized with a copy of another object of the same class.
The assignment or copy of an object only assigns or copies data members, not member functions.

 Rectangle R1;
    Rectangle R2{<!-- --> 3,4};
    Rectangle R = R1; //Default copy of the object R = 1,2

By default, objects can be assigned values.

 Rectangle R1;
    Rectangle R2{<!-- --> 3,4};
    R1 = R2; //Object assignment; R1 = 3,4

Conversion constructor

A constructor with only one parameter is called a conversion constructor.
The function of the conversion constructor is to convert data of another type into an object of a class.
Copy constructors and move constructors are also conversion constructors.
Example: Convert a rectangular object into a cuboid object.

/*cuboid class*/
class Cuboid {<!-- -->
public:
    int length = 1; //length
int width = 1; //Width
    int height = 1; //height
public:
    Cuboid() = default;
    Cuboid(int L, int W, int H): length{<!-- --> L }, width{<!-- --> W }, height{<!-- --> H } {<! -- -->};
    Cuboid(Rectangle & amp; R) //Conversion constructor
    {<!-- -->
        length = R.length;
        width = R.width;
    }
};
int main()
{<!-- -->
    Rectangle R1; //Construction without parameters
    Rectangle R2{<!-- --> 3,4}; //Construction with parameters
    Cuboid C{<!-- --> 1,2,3 };
    Cuboid C1{<!-- --> R1 }; //Displayed type conversion, conversion constructor
    C1 = R2; //Implicit type conversion and assignment
    Cuboid C2 = R1; //Implicit type conversion, copy
}

Suppress implicit type conversions for constructors
If implicit type conversion is not required, we can prevent it by declaring the constructor explicit:

 explicit Cuboid(Rectangle & amp; R)
    {<!-- -->
        length = R.length;
        width = R.width;
    }

Explicit constructors can only be used for direct initialization.

Cuboid C1{<!-- --> R1 }; //Only the conversion constructor can be called explicitly

Copy constructor

A constructor is a copy constructor if its first parameter is a reference to its own class type and any additional parameters have default values.

class MyString
{<!-- -->
private:
        char* str = nullptr;
        unsigned int MaxSize = 0; /*String storage space*/
    public:
        MyString(const MyString & amp; S); /*Copy constructor*/
};
/*Copy constructor*/
MyString::MyString(const MyString & amp; S)
{<!-- -->
    MaxSize = S.MaxSize;
    str = new char[MaxSize] {<!-- -->};
    for (unsigned int i = 0; i < MaxSize; i + + )
    {<!-- -->
       str[i] = S.str[i];
    }
}

The first argument to the copy constructor must be a reference to the object, and is usually a const reference.

Copy initialization

With copy initialization (using the equal sign or curly braces), the compiler copies the right-hand operand into the object being created.
Copy initialization occurs not only when a variable is defined using the equal sign, but also in the following situations:

  • Pass an object as an argument to a non-reference type parameter.
  • Return an object from a function whose return type is a non-reference type.
  • Use a braced list to initialize elements of an array or members of an aggregate class.

Copy assignment operator overloading

Assignment operator overloading must be defined as a member function; its left operand is bound to the implicit this parameter.
The right-hand operand of an assignment operator is passed as an explicit parameter, usually a const reference to the right-hand operand.
An assignment operator overload should usually return a reference to its left-hand operand.

class MyString
{<!-- -->
private:
        char* str = nullptr;
        unsigned int MaxSize = 0; /*String storage space*/
    public:
        MyString & amp; operator= (const MyString & amp; S); /*Copy assignment operator overloading*/
};
/*Copy assignment operator overloading*/
MyString & amp; MyString::operator= (const MyString & amp; S)
{<!-- -->
    delete[] str;
    MaxSize = S.MaxSize;
    str = new char[MaxSize] {<!-- -->};
    for (unsigned int i = 0; i < MaxSize; i + + )
    {<!-- -->
        str[i] = S.str[i];
    }
    return *this;
}

Destructor

The destructor’s job: Releases the resources used by the object and destroys the object’s non-static data members.
The destructor is a member function within the class, and its name consists of a tilde followed by the class name.
It has no return value, does not accept parameters, and cannot be overloaded.

class MyString
{<!-- -->
private:
        char* str = nullptr;
        unsigned int MaxSize = 0; /*String storage space*/
    public:
        ~MyString(); /*Destructor*/
};
/*Destructor*/
MyString::~MyString()
{<!-- -->
    if (str != nullptr)
        delete[] str;
}

The work done by the destructor

A destructor has a function body and a destructor part (the destructor part is implicit).
The destructor is implicitly called when the object goes out of scope or is released by delete.
To execute a destructor, the function body is executed first, then the members are destroyed, and finally the members are destroyed in the reverse order of initialization.
Destroying a member of a class type requires executing the member’s own destructor.
Built-in types are said to have a destructor that does nothing.
Note: Implicitly destroying a member of a built-in pointer type will not delete the object it points to.

When to call the destructor

Whenever an object is destroyed, its destructor is automatically called:

  • Variables are destroyed when they leave their scope.
  • When an object is destroyed, its members are destroyed.
  • When a container (whether a standard library container or an array) is destroyed, its elements are destroyed.
  • A dynamically allocated object is destroyed when the delete operator is applied to a pointer to it.
  • A temporary object is destroyed when the complete expression that created it ends.
  • When a reference or pointer to an object goes out of scope, the destructor does not execute.

The order in which constructors and destructors are called: those constructed first are destructed, and those constructed later are destructed first.
Equivalent to the first-in-last-out principle of the stack.

Synthetic copy operation

Synthetic copy constructor

If a class does not define its own copy constructor, the compiler will define a synthetic copy constructor.
The compiler copies each non-static member from the given object in turn into the object being created.
Built-in type members are copied directly.
Members of a class type will be copied using its copy constructor.

Synthetic copy assignment operator

If a class does not define its own copy assignment operator, the compiler will generate a synthetic copy assignment operator for it.
It assigns each non-static member of the right-hand operand to the corresponding member of the left-hand operand.
The synthetic copy assignment operator returns a reference to its left operand.

Synthetic destructor

When a class does not define its own destructor, the compiler defines a synthetic destructor for it.
The function body of the synthetic destructor is empty.

class MyString{<!-- -->
public:
    ~MyString(){<!-- --> } //Equivalent to synthetic destructor
}

After the (empty) destructor body is executed, the members are automatically destroyed.
Members are destroyed in the implicit destruction phase after the destructor body.

Use=default

We can explicitly ask the compiler to generate a synthesized version via = default.

class MyString
{<!-- -->
    private:
        char* str = nullptr;
        unsigned int MaxSize = 0;
    public:
        MyString() = default; /*Default constructor*/
        MyString(const MyString & amp; S) = default; /*Synthetic copy constructor*/
        MyString & amp; operator= (const MyString & amp; S) = default; /*Synthetic copy assignment operator*/
        ~MyString() = default; /*Synthetic destructor*/
};

When we modify the declaration of a member within a class with = default, the synthesized function will be implicitly declared inline.
If we do not want the synthesized member to be an inline function, we should only use = default for the member’s out-of-class definition.

Prevent copying

Before the release of the C++11 standard, classes prevented copies by declaring their copy constructors and copy assignment operators as private.

class MyString
{<!-- -->
    private:
        char* str = nullptr;
        unsigned int MaxSize = 0;
    public:
        MyString() = default; /*Default constructor*/
        ~MyString() = default; /*Synthetic destructor*/
    private:
        MyString(const MyString & amp; S); /*Disable copy constructor*/
        MyString & amp; operator= (const MyString & amp; S); /*Disable copy assignment operator*/
};

Define deleted functions

Under the C++11 standard, we can prevent copying by defining the copy constructor and copy assignment operator as deleted functions.
Deleted functions are functions that, although we declare them, cannot use them in any way.
Add = delete after the function’s parameter list to indicate that we want it to be defined as deleted.
= delete must appear when the function is first declared.

class MyString
{<!-- -->
    private:
        char* str = nullptr;
        unsigned int MaxSize = 0;
    public:
        MyString() = default; /*Default constructor*/
        MyString(const MyString & amp; S) = delete; /*Delete copy constructor*/
        MyString & amp; operator= (const MyString & amp; S) = delete; /*Delete copy assignment operator*/
        ~MyString() = default; /*Synthetic destructor*/
};

Classes that wish to prevent copying should define their own copy constructors and copy assignment operators using = delete rather than declaring them private.
Synthetic copy control members may be deleted
For some classes, the compiler defines these synthesized members as deleted functions:

  • A class’s synthetic destructor is defined as deleting if the destructor of a member of the class is deleting or inaccessible.
  • If the destructor of a member of the class is deleted or inaccessible, the class’s synthesized copy constructor is also defined as deleted.
  • A class’s synthetic copy constructor is defined as deleted if the copy constructor of a member of the class is deleted or inaccessible.
  • A class’s synthetic copy assignment operator is defined as deleted if the copy assignment operator of a member of the class is deleted or inaccessible, or if the class has a const or reference member.
  • If the destructor of a member of a class is deleted or inaccessible, or the class has a reference member that does not have an in-class initializer, or the class has a const member that does not have an in-class initializer and its type If a default constructor is not explicitly defined, the default constructor for the class is defined as deleted.

Essentially, what these rules mean is: if a class has data members that cannot be constructed, copied, duplicated, or destroyed by default, the corresponding member function will be defined as deleted.

Move constructor

The first parameter of the move constructor is an rvalue reference of the class type.
Any additional parameters to the move constructor must have default arguments.
Once a resource is moved, the source object must no longer point to the moved resource; ownership of those resources has been assigned to the newly created object.

class MyString
{<!-- -->
private:
        char* str = nullptr;
        unsigned int MaxSize = 0; /*String storage space*/
    public:
        MyString( MyString & amp; & amp; S) noexcept; /*Move constructor*/
};
/*Move constructor*/
MyString::MyString(MyString & amp; & amp; S) noexcept
{<!-- -->
    delete[] str;
    MaxSize = S.MaxSize;
    str =S.str;
    S.str = nullptr;
}

If the move constructor does not throw any exception, we should mark it as noexcept.
In a constructor, noexcept appears between the colon at the beginning of the argument list and the initialization list.

Move assignment operator overloading

The move assignment operator overload must be defined as a member function; its left operand is bound to the implicit this parameter.
The right-hand operand of an assignment operator is passed as an explicit parameter, usually an rvalue reference.
An assignment operator overload should usually return a reference to its left-hand operand.

class MyString
{<!-- -->
private:
        char* str = nullptr;
        unsigned int MaxSize = 0; /*String storage space*/
    public:
        MyString( MyString & amp; & amp; S) noexcept; /*Move constructor*/
};
/*Move assignment operator overloading*/
MyString & amp; MyString::operator= (MyString & amp; & amp; S) noexcept
{<!-- -->
    delete[] str;
    MaxSize = S.MaxSize;
    str = S.str;
    S.str = nullptr;
    return *this;
}

If the move assignment operator does not throw any exception, we should mark it as noexcept.

Synthetic move operations

If a class defines its own copy constructor, copy assignment operator, or destructor, the compiler will not synthesize a move constructor or move assignment operator for it.
Only when a class does not define any of its own versions of copy-controlled members and all of its data members can be move constructed or moved assigned, the compiler will synthesize a move constructor or move assignment operator for it.
The compiler can move members of built-in types.
If a member is of a class type and the class has a corresponding move operation, the compiler can also move the member.
Move operations are never implicitly defined as a deleted function.
If we explicitly ask the compiler to generate a move operation for =default, and the compiler cannot move all members, the compiler will define the move operation as a deleted function.
With one important exception, when defining a synthesized move operation as a deleted function follows the following guidelines:

  • The conditions for a move constructor to be defined as a deleted function are: a class member defines its own copy constructor and does not define a move constructor, or a class member does not define its own copy constructor and the compiler cannot synthesize a move for it Constructor. The situation is similar with the move assignment operator.
  • If a class member’s move constructor or move assignment operator is defined as deleted or is inaccessible, the class’s move constructor or move assignment operator is defined as deleted.
  • If a class’s destructor is defined as deleted or inaccessible, the class’s move constructor is defined as deleted.
  • If any class members are const or references, the class’s move assignment operator is defined as deleted.

A class that defines a move constructor or move assignment operator must also define its own copy operation; otherwise, these members are defined as deleted by default.