Assignment operator (operator=) is overloaded to allow assigning one object to another existing object either by copy or move (move constructor and assignment).

Copy assignment and copy constructor seem to perform similar kind of operation but there is a difference. Copy constructor copies data members from object to initialize a non-existing object, on the other hand, copy assignment copies data members from object to existing object.

Overloading assignment operator

Overloading assignment operator is fairly straightforward as can be shown below:

#include <iostream>
 
class Int
{
    int m_value{};
 
public:
    Int() = default;
    Int(int value)
        : m_value{value}
    {
    }
 
    Int &operator=(const Int &i)
    {
        m_value = i.m_value;
        return *this;
    }
 
    friend std::ostream &operator<<(std::ostream &out, const Int &v);
};
 
std::ostream &operator<<(std::ostream &out, const Int &v)
{
    return out << v.m_value;
}
 
int main(int argc, char const *argv[])
{
 
    Int v1{1};
 
    Int v2{2};
 
    v2 = v1;
 
    std::cout << v1 << '\n';
    std::cout << v2 << '\n';
    return 0;
}

Overloaded operator takes Int object and copies data members of passed object to implicit object’s data members and returns the implicit object.

Assignment to itself

It is possible to assign an object to itself and there is no problem with that. However, it leads to an issue when object deals with dynamic memory.

For example:

#include <iostream>
#include <algorithm>
 
class Array
{
    int *m_arr{nullptr};
    int m_length{};
 
public:
    Array() = default;
    Array(const int *arr, int length)
        : m_length{length}
    {
        if (length)
        {
            m_arr = new int[length];
            std::copy_n(arr, length, m_arr);
        }
    }
 
    ~Array() { delete[] m_arr; }
 
    Array &operator=(const Array &other)
    {
        // release memory of old data.
        if (m_arr)
        {
            delete[] m_arr;
        }
 
        m_length = other.m_length;
        m_arr = nullptr;
 
        // alloc new memory
        if (m_length)
        {
            m_arr = new int[m_length];
        }
 
        std::copy_n(other.m_arr, m_length, m_arr);
        return *this;
    }
 
    friend std::ostream &operator<<(std::ostream &out, const Array &arr);
};
 
std::ostream &operator<<(std::ostream &out, const Array &arr)
{
    out << "[ ";
    for (int i{0}; i < arr.m_length; ++i)
    {
        out << arr.m_arr[i] << " ";
    }
 
    out << "]\n";
    return out;
}
 
int main(int argc, char const *argv[])
{
    int a[5]{1, 2, 3, 4, 5};
    Array arr{a, 5};
 
    arr = arr;
    std::cout << arr << '\n';
    return 0;
}

When we execute the program, we would not get expected outcome. It happened because we are assigning arr to itself and we look at the implementation of operator=, first we are deleting the memory occupied by the object before assigning it new values from another object. In case of itself assignment, the object memory is deleted and data is lost and when new memory is allocated with garbage values, we are assigning them to the object. Thus, the program gives unexpected outcomes.

We can solve this by adding a condition:

    Array &operator=(const Array &other)
    {
        //checks whether doing self assignment
        if (this == &other)
        {
            return *this;
        }
 
        // release memory of old data.
        if (m_arr)
        {
            delete[] m_arr;
        }
 
        m_length = other.m_length;
        m_arr = nullptr;
 
        // alloc new memory
        if (m_length)
        {
            m_arr = new int[m_length];
        }
 
        std::copy_n(other.m_arr, m_length, m_arr);
        return *this;
    }

So, if other object is the target object itself, then return the object.

There is a better approach to solve this problem of conditional just to check assignment and the code duplication for dynamic allocation and copy, which can be seen in copy constructor operator=. That approach is copy-and-swap idiom.

Implicit copy assignment operator

If we do not defined a copy assignment operator, compiler adds a default copy assignment operator that does member wise copy (like implicit copy constructor does). If we do not intent to have copy assignment operator, we can delete it using:

class Int
{
    int m_value{};
 
public:
    Int() = default;
    Int(int value)
        : m_value{value}
    {
    }
 
    Int &operator=(const Int &i) = delete;
};

References

  1. https://www.learncpp.com/cpp-tutorial/overloading-the-assignment-operator/