Menu
Coddy logo textTech

Assignment Operator Overload

Part of the Object Oriented Programming section of Coddy's C++ journey — lesson 44 of 104.

The copy assignment operator (=) is called when you assign one existing object to another. Unlike the copy constructor (which creates a new object), the assignment operator works with an object that already exists and may have resources that need to be cleaned up first.

class Buffer {
    int* data;
    size_t size;
    
public:
    Buffer(size_t s) : size(s), data(new int[s]) {}
    ~Buffer() { delete[] data; }
    
    Buffer& operator=(const Buffer& other) {
        if (this != &other) {              // 1. Self-assignment check
            delete[] data;                  // 2. Clean up existing resource
            size = other.size;              // 3. Copy the data
            data = new int[size];
            for (size_t i = 0; i < size; ++i)
                data[i] = other.data[i];
        }
        return *this;                       // 4. Return *this
    }
};

The self-assignment check (this != &other) is critical. Without it, b = b; would delete the data before trying to copy it, causing undefined behavior.

The operator returns *this by reference to enable chaining like a = b = c;. This matches how built-in types behave.

For classes managing resources, you'll often implement both copy and move assignment operators:

Buffer& operator=(Buffer&& other) noexcept {
    if (this != &other) {
        delete[] data;           // Clean up current resource
        data = other.data;       // Steal the resource
        size = other.size;
        other.data = nullptr;    // Leave source in valid state
        other.size = 0;
    }
    return *this;
}

The move assignment operator transfers ownership instead of copying, making operations like vec[0] = createBuffer(); much more efficient.

challenge icon

Challenge

Easy

Let's build a DynamicString class that manages a dynamically allocated character array and properly handles assignment between existing objects. This is a practical scenario where you need to safely transfer string data from one object to another without memory leaks or dangling pointers.

You'll create two files to organize your code:

  • DynamicString.h: Define a DynamicString class that stores a C-style string using dynamic memory. Your class should manage:
    • A private char* pointer for the string data
    • A private size_t for the length
    • A constructor that takes a const char* and creates a deep copy
    • A destructor that properly frees the allocated memory
    • A copy assignment operator that performs a deep copy with self-assignment protection
    • A move assignment operator that transfers ownership efficiently
    • A c_str() method that returns the internal string (const)
    • A length() method that returns the string length (const)

    Your copy assignment operator must follow the four essential steps: check for self-assignment, clean up existing resources, copy the new data, and return *this. The move assignment operator should steal the source's pointer and leave the source in a valid empty state (nullptr with length 0).

  • main.cpp: Read three strings from input (each on a separate line). Create three DynamicString objects: str1 with the first input, str2 with the second input, and str3 with the third input.

    Demonstrate your assignment operators:

    1. Print the initial state of all three strings
    2. Use copy assignment: str1 = str2;
    3. Print str1 and str2 after copy assignment (both should have the same content)
    4. Use move assignment: str2 = std::move(str3);
    5. Print str2 after move assignment, and str3's length (should be 0 after being moved from)

    Output format:

    Initial:
    str1: <value>
    str2: <value>
    str3: <value>
    After copy (str1 = str2):
    str1: <value>
    str2: <value>
    After move (str2 = std::move(str3)):
    str2: <value>
    str3 length: 0

Use <cstring> for strlen and strcpy. Mark your move assignment operator as noexcept. Include <utility> in main.cpp for std::move. Don't forget header guards in your header file.

Cheat sheet

The copy assignment operator (=) assigns one existing object to another. It must handle cleanup of existing resources before copying new data.

Copy Assignment Operator Structure

ClassName& operator=(const ClassName& other) {
    if (this != &other) {              // 1. Self-assignment check
        // 2. Clean up existing resources
        // 3. Copy data from other
    }
    return *this;                       // 4. Return *this
}

Example with Dynamic Memory

class Buffer {
    int* data;
    size_t size;
    
public:
    Buffer& operator=(const Buffer& other) {
        if (this != &other) {
            delete[] data;                  // Clean up existing resource
            size = other.size;
            data = new int[size];
            for (size_t i = 0; i < size; ++i)
                data[i] = other.data[i];
        }
        return *this;
    }
};

The self-assignment check (this != &other) prevents deleting data before copying it in cases like b = b;.

Returning *this by reference enables chaining: a = b = c;.

Move Assignment Operator

The move assignment operator transfers ownership instead of copying, improving efficiency:

ClassName& operator=(ClassName&& other) noexcept {
    if (this != &other) {
        // Clean up current resource
        // Steal the resource from other
        // Leave other in valid state (nullptr, 0, etc.)
    }
    return *this;
}

Example Move Assignment

Buffer& operator=(Buffer&& other) noexcept {
    if (this != &other) {
        delete[] data;
        data = other.data;       // Steal the resource
        size = other.size;
        other.data = nullptr;    // Leave source valid
        other.size = 0;
    }
    return *this;
}

Mark move assignment as noexcept for optimal performance with standard containers.

Try it yourself

#include <iostream>
#include <string>
#include <utility>
#include "DynamicString.h"

using namespace std;

int main() {
    // Read three strings from input
    string input1, input2, input3;
    getline(cin, input1);
    getline(cin, input2);
    getline(cin, input3);

    // Create three DynamicString objects
    DynamicString str1(input1.c_str());
    DynamicString str2(input2.c_str());
    DynamicString str3(input3.c_str());

    // TODO: Print initial state of all three strings
    // Format:
    // Initial:
    // str1: <value>
    // str2: <value>
    // str3: <value>

    // TODO: Use copy assignment: str1 = str2;

    // TODO: Print str1 and str2 after copy assignment
    // Format:
    // After copy (str1 = str2):
    // str1: <value>
    // str2: <value>

    // TODO: Use move assignment: str2 = std::move(str3);

    // TODO: Print str2 after move, and str3's length
    // Format:
    // After move (str2 = std::move(str3)):
    // str2: <value>
    // str3 length: 0

    return 0;
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming