Defining Constructors

Rules of overloading default constructors and operators

Rule of 0

Classes have neither custom destructors, copy/move constructors or copy/move assignment operators.`

Rule of 3

If you implement a custom version of any of these, you implement all of them.
Destructor, Copy constructor, copy assignment

Rule of 5

If you implement a custom move constructor or the move assignment operator, you need to define all 5 of them. Needed for move semantics.
Destructor, Copy constructor, copy assignment, move constructor, move assignment

Rule of four and a half

Same as Rule of 5 but with copy and swap idiom. With the inclusion of the swap method, the copy assignment and move assignment merge into one assignment operator.
Destructor, Copy constructor, move constructor, assignment, swap (the half part)

Example (Rule of 5)

#include <iostream>
#include <cstring>

class String {
public:
    // Constructor
    String(const char* str = "") {
        size = std::strlen(str);
        data = new char[size + 1];
        std::strcpy(data, str);
    }

    // Destructor
    ~String() {
        delete[] data;
    }

    // Copy Constructor
    String(const String& other) {
        size = other.size;
        data = new char[size + 1];
        std::strcpy(data, other.data);
    }

    // Copy Assignment Operator
    String& operator=(const String& other) {
        if (this != &other) {
            delete[] data; // Free existing resource
            size = other.size;
            data = new char[size + 1];
            std::strcpy(data, other.data);
        }
        return *this;
    }

    // Move Constructor
    String(String&& other) noexcept : data(other.data), size(other.size) {
        other.data = nullptr; // Leave other in a valid state
        other.size = 0;
    }

    // Move Assignment Operator
    String& operator=(String&& other) noexcept {
        if (this != &other) {
            delete[] data; // Free existing resource
            data = other.data;
            size = other.size;
            other.data = nullptr; // Leave other in a valid state
            other.size = 0;
        }
        return *this;
    }


private:
    char* data;
    size_t size;
};

Example (Rule of 4.5 - Using copy and swap Idiom)

#include <iostream>
#include <cstring>
#include <utility> // for std::swap

class String {
public:
    // Constructor
    String(const char* str = "") {
        size = std::strlen(str);
        data = new char[size + 1];
        std::strcpy(data, str);
    }

    // Destructor
    ~String() {
        delete[] data;
    }

    // Copy Constructor
    String(const String& other) : data(nullptr), size(0) {
        *this = other; // Use copy assignment
    }

    // Copy Assignment Operator
    String& operator=(String other) {
        swap(other); // Use the swap idiom
        return *this;
    }

    // Move Constructor
    String(String&& other) noexcept : data(other.data), size(other.size) {
        other.data = nullptr; // Leave other in a valid state
        other.size = 0;
    }

    // Move Assignment Operator
    String& operator=(String&& other) noexcept {
        swap(other); // Use the swap idiom
        return *this;
    }

    // Swap function
    void swap(String& other) noexcept {
        std::swap(data, other.data);
        std::swap(size, other.size);
    }

private:
    char* data;
    size_t size;
};

References

https://en.cppreference.com/w/cpp/language/rule_of_three