C++

C++ : Move Assignment Operator

Move Semantics

Consider the below example of swapping values of a and b. We note that redundant copies of variables a and b are created just for swapping the values.
A copying operation could be expensive as it involves calling a function, allocating a memory and running a loop; all of which could be avoided using std :: move.

template <class T> 
void Swap (T& a, T& b) {
    T tmp(a);   // We have two copies of a (tmp and a)
    a = b;      // now we have two copies of b (a and b)
    b = tmp;    // now we have two copies of tmp (b and tmp)
}
std :: move
- std :: move ( T&& obj ) enforces move semantics to transfer the resources from one object to another object. The std :: move function accepts either an lvalue or rvalue argument, and returns a rvalue reference without invoking a copy constructor.
 obj = std :: move ( tmp_obj ); 
Note : After the std :: move ( tmp_obj ) call, the tmp_obj loses its value.

Swap operation using std::move eliminates creation of redundant copies.

template <class T> 
void Swap (T& a, T& b) {
    T tmp(std::move(a));
    a = std::move(b);   
    b = std::move(tmp);
}

Move assignment


String class : Move assignment operator
- Parameter : The move assignment operator for a class takes an rvalue reference ( && ) to the class type as its parameter.
String& operator = (String&& obj) {
. . .
}
- Check before assigning : To prevent an object getting assigned to itself, a conditional block is used.
- Do the assignment
- Release the source pointer to avoid multiple free operations by the destructor.
if ( this != &obj ) { 

   // Do the assignment
   // Copy the source length and the source pointer into the target.
   m_len = obj.m_len;
   m_buff = obj.m_buff;

   // Release the pointer from the source object to avoid the double free operation by the destructor.
   m_len = 0;
   m_buff = nullptr;
}

C++ program for a String class demonstrates the move assignment operator.

#include <iostream>
#include <vector>
#include <string>

int main () {

    std :: string noddy = "noddy";
    std :: string deltoid = "deltoid";
    std :: vector < std :: string> str_vec;

    std :: cout << "\nBefore push operation" << std :: endl;
    std :: cout << "noddy : [" << noddy << "]" << std :: endl;
    std :: cout << "deltoid : [" << deltoid << "]" << std :: endl;
    std :: cout << "\nContent of vector " << std :: endl;

    str_vec.push_back (noddy); // A copy operation
    str_vec.push_back (std :: move (deltoid)); // A move operation

    for (const auto& str : str_vec ) {
         std :: cout << str << std :: endl;
    }

    std :: cout << "\nAfter push operation" << std :: endl;
    std :: cout << "noddy : [" << noddy << "]" << std :: endl;
    std :: cout << "deltoid : [" << deltoid << "]" << std :: endl;

    return 0;
}

Output

Before push operation
noddy : [noddy]
deltoid : [deltoid]

Content of vector 
noddy
deltoid

After push operation
noddy : [noddy]
deltoid : []
#include<iostream>
#include<cstring>

class String {

    private:
    u_int m_len;
    char* m_buff;

    public:
    // Default constructor
    String () {
        std :: cout << "Default constructor of String class got called." << std :: endl;
        m_len = 0;
    }
        m_buff = new char;
        m_buff[0] = '\0';

    // Parameterized constructor
    String (const char * str) {
        std :: cout << "Parameterized constructor of String class got called." << std :: endl;
        m_len = strlen(str);
        m_buff = new char[m_len + 1];
        strcpy(m_buff, str);
    }

    String (String&& obj) {
        m_len = obj.m_len;
        m_buff = obj.m_buff;

        obj.m_len = 0;
        obj.m_buff = nullptr;
    }

    // Overloading = operator for String class
    String& operator = (const String& obj) {
        std :: cout << "Overloaded assignment operator for String class got called." << std :: endl;
        if (this == &obj) { // If the source obj is same as the destn object, no need to copy.
            return (*this);
        } else {
            m_len = obj.m_len;
            delete [] m_buff; // Delete the old buffer of the destination object.
            m_buff = new char[m_len+1];
            strcpy(m_buff, obj.m_buff);
            return (*this);
        }
    }

    // Move assignment operator for String class
    String& operator = (String&& obj) {
        std :: cout << "Move assignment operator for String class got called." << std :: endl;
        if (this != &obj) { // If the source obj is same as the destn object, no need to move.
            //  Copy the source length and the source pointer into the target. 
            m_len = obj.m_len;
            m_buff = obj.m_buff;

            // Release the pointer from the source object to avoid the double free operation
            // by the destructor.
            obj.m_len = 0; 
            obj.m_buff = nullptr;
        }
        return (*this);
    }

    ~String() {
        std :: cout << "Destructor String class got called." << std :: endl;
        if (m_buff) {
            std :: cout << "Deleting the buffer." << std :: endl;
           delete [] m_buff;
        }
    }

    void Display() {
        std :: cout << m_buff << std :: endl;
    }
};

int main() {

   String deltoid("Deltoid");
   String deltoid_right;

   deltoid_right = std :: move(deltoid); // Call the move assignment operator.
   // deltoid_right = deltoid; // Call the overloaded assignment operator.

   std :: cout << "Main ends. Now returning." << std :: endl;
   return 0;
}

Output

Parameterized constructor of String class got called.
Default constructor of String class got called.
Move assignment operator for String class got called.
Main ends. Now returning.
Destructor String class got called.
Deleting the buffer.
Destructor String class got called.


Copyright (c) 2019-2023, Algotree.org.
All rights reserved.