Overload of C + + assignment operator '=' (shallow copy, deep copy)

Posted by azazelis on Sat, 07 Dec 2019 07:34:40 +0100

01 assignment operator overload requirements

Sometimes you want the types on both sides of the assignment operator to be mismatched. For example, assign an int type variable to a Complex object, or assign a char * type string to a string object. In this case, you need to overload the assignment operator '='.

Note that the assignment operator = can only be overloaded as a member function.

02 example of overloaded assignment operator

Let's explain the overloaded function of assignment operator with an example of customizing a string class code of our own.

class MyString // String class
{
public:
    // Constructor, initializing 1-byte character by default
    MyString ():m_str(new char[1])
    {
        m_str[0] = 0;
    }
    
    // Destructors, freeing resources
    ~MyString()
    {
        delete [] m_str;
    }
    
    const char* c_str()
    {
        return m_str;
    }
    
    // Assignment operator overload function
    // Overload = makes obj = "Hello" tenable
    MyString & operator= (const char *s)
    {
        // Release old string resources
        delete [] m_str;
        
        // Space size of new string generated, more length + 1 to store \ 0
        m_str = new char[strlen(s) +1 ];
        
        // Copy the contents of the new string
        strcpy(m_str, s);
        
        // Return the object
        return *this;
    }
    
private:
    char * m_str; // String pointer
};

int main() {
    
    MyString s;
    
    s = "Hello~"; // Equivalent to s.operator=("Hello ~");
    std::cout << s.c_str()  << std::endl;
    
    // MyString s2 = "Hello!"; / / if this statement is uncommented, it will compile and report an error
    
    s = "Hi~"; // Equivalent to s.operator=("Hi ~");
    std::cout << s.c_str()  << std::endl;

    return 0;
}

Output results:

Hello~
Hi~

When the = operator function is overloaded, the s = "Hello ~"; statement is equivalent to the s.operator=("Hello ~");.

It should be noted that the above MyString s2 = "Hello!"; statement is actually an initialization statement, not an assignment statement. Because it is an initialization statement, the constructor needs to be called for initialization. Then a constructor with the char * parameter is required. Since we have not defined this constructor, there will be compilation errors.

03 shallow copy and deep copy

Based on the above example, suppose we want to implement the last statement:

MyString s1,s2;
s1 = "this"; // Call overloaded assignment statement
s2 = "that"; // Call overloaded assignment statement
s1 = s2; // How to achieve this??

s1 = s2; the purpose of the statement is that the string placed by s1 object is the same as the string placed by s2 object. Because the similarity on both sides of the = sign is the object, the compiler will use the native assignment operator function, but this native assignment operator function is very dangerous for the object with pointer member variable!

Shallow copy

If the original assignment operator function is used to assign objects with pointer member variables, the pointer addresses of the two objects will be the same, that is to say, the pointer member variables of the two objects point to the same address, which is a shallow copy.

When an object releases a pointer member variable, the address pointed to by another object's pointer member variable is empty. When the object is used again, the program will crash, because the pointer member function of the object is already an illegal pointer!

Deep copy

If there are pointer member variables in the object, we need to use the native assignment operator function to prevent the occurrence of program error.

Therefore, add the following member functions to the class MyString class:

MyString & operator=(const MyString & s)
{
    // Release old string resources
    delete [] m_str;
    
    // Space size of new string generated, more length + 1 to store \ 0
    m_str = new char[strlen(s.m_str) +1 ];
    
    // Copy the contents of the new string
    strcpy(m_str, s.m_str);
    
    // Return the object
    return *this;
}

Is that enough? Is there anything else to improve?

We are considering the following statement:

MyString s;
s = "Hello";
s = s; // Is there a problem?

Is there a problem with the last statement?

s = s; equivalent to s.operator=(s). Since s and s are the same object, it's unnecessary to perform the overloaded function of assignment = completely. Let's add another judgment. When the left and right sides are the same object, it's better to return the object directly:

MyString & operator=(const MyString & s)
{
    // When the left and right sides are the same object, directly return the object
    if(this == &s)
        return *this;

    delete [] m_str;
    m_str = new char[strlen(s.m_str) +1 ];
    strcpy(m_str, s.m_str);
    return *this;
}

Discussion on operator = return value type

  • How about void?
  • How about MyString?
  • Why mystring &?

When we overload an operator, the good style is to keep the original characteristics of the operator as much as possible

Consider:

  • a = b = c; the order of this assignment statement is first b = c, then a = (b = c). If the returned void type, then a = (void) is obviously not valid;
  • (a = b) = c; this assignment statement will modify the value of A. if the returned type is a MyString object, then the value of a cannot be modified.

They are equivalent to:

  • a.operator=(b.operator=(c));
  • (a.operator=(b)).operator=(c);

So to sum up, operator = return value type is mystring & which is better.

04 copy constructor

Is the above MyString class OK?

MyString s;
s = "Hello";
MyString s1(s); // To consider this situation, overload the copy constructor

If the default copy (copy) constructor is used, there will be problems for objects with pointer member variables, because the default copy (copy) constructor will cause pointer member variables of two objects to point to the same space.

Therefore, it is necessary to overload the copy constructor and realize the way of deep copy:

MyString (const MyString &s)
{
    m_str = new char[strlen(s.m_str) + 1];
    strcpy(m_str, s.m_str);
}

05 summary

All the final codes are as follows:

class MyString // String class
{
public:
    // Constructor, initializing 1-byte character by default
    MyString ():m_str(new char[1])
    {
        m_str[0] = 0;
    }
    
    // Copy constructor
    MyString (const MyString &s)
    {
        m_str = new char[strlen(s.m_str) + 1];
        strcpy(m_str, s.m_str);
    }
    
    // Destructors, freeing resources
    ~MyString()
    {
        delete [] m_str;
    }
    
    const char* c_str()
    {
        return m_str;
    }
    
    // Assignment operator overload function
    // Overload = makes obj = "Hello" tenable
    MyString & operator= (const char *s)
    {
        // Release old string resources
        delete [] m_str;
        
        // Space size of new string generated, more length + 1 to store \ 0
        m_str = new char[strlen(s) +1 ];
        
        // Copy the contents of the new string
        strcpy(m_str, s);
        
        // Return the object
        return *this;
    }
    
    // Assignment operator overload function
    // Overloading the = number enables obj 1 = obj 2 to hold
    MyString & operator=(const MyString & s)
    {
        // When the left and right sides are the same object, directly return the object
        if(this == &s)
            return *this;
    
        delete [] m_str;
        m_str = new char[strlen(s.m_str) +1 ];
        strcpy(m_str, s.m_str);
        return *this;
    }
    
private:
    char * m_str; // String pointer
};

int main() 
{
    
    MyString s1,s2;
    
    s1 = "Hello~"; // Equivalent to s1.operator=("Hello ~");
    std::cout << s1.c_str()  << std::endl;
    
    
    s2 = "Hi~"; // Equivalent to s2.operator=("Hi ~");
    std::cout << s2.c_str()  << std::endl;
    
    s1 = s2;   // Equivalent to s1.operator=(s2);
    std::cout << s1.c_str()  << std::endl;
    
    MyString s3(s1);  // copy constructor 
    std::cout << s3.c_str()  << std::endl;

    return 0;
}

The output is as follows:

Hello~
Hi~
Hi~
Hi~

Topics: C++