Skillversitas

Copy Constructor and Move Semantics

A copy constructor is a constructor that takes an object of the same class as a parameter. It is used to create a new object as a copy of an existing one. In modern C++, move semantics allow transferring a resource from one object to another without making a copy, making it more efficient.

The Rule of Three (C++98) states that if a class has any one of the following: a destructor, a copy constructor, or a copy assignment operator, then the class must have all three of them. The Rule of Five (C++11) adds the move constructor and move assignment operator.

Code Example

#include <iostream>
#include <cstring>
using namespace std;

class MyString {
private:
    char* data;
    int length;

public:
    MyString(const char* str = "") {
        length = strlen(str);
        data = new char[length + 1];
        strcpy(data, str);
        cout << "Constructor: \"" << data << "\"" << endl;
    }

    MyString(const MyString& other) {
        length = other.length;
        data = new char[length + 1];
        strcpy(data, other.data);
        cout << "Copy constructor: \"" << data << "\"" << endl;
    }

    MyString(MyString&& other) noexcept {
        data = other.data;
        length = other.length;
        other.data = nullptr;
        other.length = 0;
        cout << "Move constructor: \"" << data << "\"" << endl;
    }

    MyString& operator=(const MyString& other) {
        if (this != &other) {
            delete[] data;
            length = other.length;
            data = new char[length + 1];
            strcpy(data, other.data);
            cout << "Copy assignment: \"" << data << "\"" << endl;
        }
        return *this;
    }

    MyString& operator=(MyString&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data;
            length = other.length;
            other.data = nullptr;
            other.length = 0;
            cout << "Move assignment: \"" << data << "\"" << endl;
        }
        return *this;
    }

    ~MyString() {
        cout << "Destructor: \"" << (data ? data : "nullptr") << "\"" << endl;
        delete[] data;
    }

    void display() {
        cout << "String: \"" << (data ? data : "nullptr") << "\", Length: " << length << endl;
    }
};

MyString createString() {
    MyString temp("Hello World");
    return temp;
}

int main() {
    cout << "=== 1. Creating object from literal ===" << endl;
    MyString s1("Hello");
    s1.display();

    cout << "\n=== 2. Copy constructor ===" << endl;
    MyString s2(s1);
    s2.display();

    cout << "\n=== 3. Copy assignment ===" << endl;
    MyString s3("Old");
    s3 = s1;
    s3.display();

    cout << "\n=== 4. Move constructor ===" << endl;
    MyString s4(createString());
    s4.display();

    cout << "\n=== 5. Move assignment ===" << endl;
    MyString s5("Temp");
    s5 = createString();
    s5.display();

    cout << "\n=== Program finished ===" << endl;
    return 0;
}

Code Explanation

  1. MyString(const char* str = "") - A constructor with a default argument. Allocates memory and copies the input string into the internal buffer.

  2. MyString(const MyString& other) - The copy constructor. Allocates new memory and copies the data from the source object. This is a deep copy, not a shallow copy.

  3. MyString(MyString&& other) noexcept - The move constructor. Transfers ownership of the data pointer from other to the new object without allocating new memory. other.data is set to nullptr.

  4. MyString& operator=(const MyString& other) - The copy assignment operator. Deletes the old data, allocates new memory, and copies the data. Checks for self-assignment with this != &other.

  5. MyString& operator=(MyString&& other) noexcept - The move assignment operator. Transfers the resource from other without making a new copy.

  6. noexcept - Indicates that the function does not throw an exception. Important for move operations so the compiler can optimize them.

  7. createString() - A function that returns a MyString object. Return value optimization (RVO) or the move constructor is used to transfer the object.

  8. nullptr - After a move, the source object is in a valid but empty state. The source object's destructor will not free the memory because it is already nullptr.

Everyday Use Cases

Imagine you have a book (a resource). When you copy the book (copy), you have to buy a new book and copy all of its pages. It takes time and cost. But when you move the book (move), you simply hand the original book to someone else without making a new copy. The other person now has the book, and you no longer have one. This is far more efficient.

Conclusion

A copy constructor and copy assignment make copies of the data (deep copy). A move constructor and move assignment transfer ownership of a resource without copying (shallow transfer). Rule of Three: if you have any one of destructor/copy constructor/copy assignment, create all three. Rule of Five (C++11): add a move constructor and move assignment for efficiency. Always use noexcept on move operations.