Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

C++ Classes and Objects: Overloading the Assignment Operator

Tech 1

Operator Overloading Fundamentals

Custom data types (classes) lack built-in operator behavior like integer or floating-point types. Operator overloading allows us to define standard operator behavior for user-defined classes, making code more readable and concise by letting us use familiar syntax with custom types.

Key Differences: Operator Overloading vs Function Overloading

Many beginners confuse these two features, but they serve distinct purposes:

  • Function Overloading: Multiple functions share the same name within the same scope, with different parameter lists (type or count). The compiler selects the correct function based on the arguments passed. Its goal is to let a single function name work with multiple data types.
  • Operator Overloading: A specialized form of overloading that redefines the behavior of existing C++ operators for custom classes. Operator overload functions are named operator followed by the target operator symbol (e.g., operator+). Unlike regular functions, their sole purpose is to let custom types use standard operator syntax.

Core Characteristics of Operator Overloading

  1. Implemented via either member functions or global (free) functions
  2. Return type matches the expected result of the operator operation
  3. Parameter count depends on the operator: member functions have an implicit this pointer, so they take one fewer explicit parameter than the arity of the operator
  4. Five operators cannot be overloaded: *, ::, sizeof, ?:, and .

Member Function Operator Overload Example

Member function overloads have an implicit this pointer pointing to the left-hand side operand, so they only require explicit parameters for the right-hand side operands:

// Member function operator overload for vector addition
class Vector2 {
public:
    int x = 0;
    int y = 0;

    int operator+(const Vector2& rhs) {
        return this->x + rhs.x + this->y + rhs.y;
    }
};

int main() {
    Vector2 vec1;
    Vector2 vec2;
    int total = vec1 + vec2;
    std::cout << "Combined vector components sum: " << total << std::endl;
    return 0;
}

Note: A member function operator+ cannot take two explicit parameters, as the implicit this counts as the first operand. For two-operand operators like +, use a global function if you need to handle both operands explicitly.

Global Function Operator Overload Example

Global overloads have no implicit this pointer, so they take all operands as explicit parameters. If you need to acess private class members, declare the overload function as a friend of the class:

class Point {
private:
    int posX;
    int posY;
public:
    Point() : posX(5), posY(15) {}

    // Declare as friend to access private members
    friend bool operator==(const Point& lhs, const Point& rhs);
};

bool operator==(const Point& lhs, const Point& rhs) {
    return (lhs.posX == rhs.posX) && (lhs.posY == rhs.posY);
}

int main() {
    Point p1;
    Point p2;
    std::cout << "Points are equal (1 = true, 0 = false): " << (p1 == p2) << std::endl;
    return 0;
}

Assignment Operator Overloading

The assignment operator (=) is used to copy the value of one existing object to another pre-existing object. The default compiler-generated assignment operator performs a shallow (bitwise) copy, which can cause issues for classes with dynamic memory or nested custom type members. Overloading the assignment operator lets you define safe, correct copying behavior for your class.

Key Differences: Asssignment Overload vs Copy Constructor vs General Operator Overload

Feature Copy Constructor Assignment Operator Overload General Operator Overload
Use Case Creates a new object as a copy of an existing one Modifies an existing object to match another's state Redefines behavior for any standard operator for custom types
Timing Called when a new object is initialized Caled when an existing object is assigned to Depends on the operator used
Signature Takes a const reference to the source object Returns a reference to the current object, takes a const reference to the source Varies based on the operator

Basic Assignment Operator Overload Example

class CalendarDate {
private:
    int year;
    int month;
    int day;
public:
    // Default constructor
    CalendarDate(int y = 2005, int m = 5, int d = 25) : year(y), month(m), day(d) {}

    // Copy constructor
    CalendarDate(const CalendarDate& other) : year(other.year), month(other.month), day(other.day) {}

    // Assignment operator overload
    CalendarDate& operator=(const CalendarDate& source) {
        // Self-assignment check to avoid accidental data loss
        if (this != &source) {
            year = source.year;
            month = source.month;
            day = source.day;
        }
        return *this;
    }

    void printDate() {
        std::cout << year << "-" << month << "-" << day << std::endl;
    }
};

int main() {
    CalendarDate date1(2024, 7, 12);
    CalendarDate date2(2021, 6, 26);

    // Copy constructor: creates date3 as a copy of date2
    CalendarDate date3(date2);

    // Assignment overload: modifies date4 to match date1
    CalendarDate date4;
    date4 = date1;

    date1.printDate();
    date2.printDate();
    date3.printDate();
    date4.printDate();
    return 0;
}

Chained Assignment Support

C++ supports chained assignment (e.g., a = b = c), which we can enable by returning a reference to the current object from the assignment overload:

#include <iostream>

class CalendarDate {
private:
    int year;
    int month;
    int day;
public:
    CalendarDate(int y = 2005, int m = 5, int d = 25) : year(y), month(m), day(d) {}

    CalendarDate& operator=(const CalendarDate& source) {
        if (this != &source) {
            year = source.year;
            month = source.month;
            day = source.day;
        }
        return *this;
    }

    void printDate() {
        std::cout << year << "-" << month << "-" << day << std::endl;
    }
};

int main() {
    CalendarDate date1(2023, 7, 10);
    CalendarDate date2, date3;

    // Chained assignment: evaluates right-to-left
    date3 = date2 = date1;

    date1.printDate();
    date2.printDate();
    date3.printDate();
    return 0;
}

The chained assignment works because each assignment returns a reference to the left-hand operand, which becomes the right-hand operand for the next assignment operation.

Default Compiler-Generated Assignment Operator

The C++ compiler automatically generates a default assignment operator for any class that does not define its own. This default operator performs a shallow bitwise copy of all member variables:

  1. Built-in type members: Directly copies the raw bytes of the member variable
  2. Custom type members: Calls the custom type's own assignment operator overload

Default Assignment for Built-In Members

class SimpleDate {
private:
    int year;
    int month;
    int day;
public:
    SimpleDate(int y = 2005, int m =5, int d=25) : year(y), month(m), day(d) {}
    void printDate() {
        std::cout << year << "-" << month << "-" << day << std::endl;
    }
};

int main() {
    SimpleDate holiday(2024,7,12);
    SimpleDate copyHoliday;
    // Uses compiler-generated default assignment overload
    copyHoliday = holiday;
    copyHoliday.printDate();
    return 0;
}

This code will correctly copy all member values without an explicitly defined assignment overload, as the default shallow copy works for built-in types.

Default Assignment for Custom Type Members

When a class contains a custom type member, the compiler-generated assignment overload will call that member's assignment operator:

class IntegerWrapper {
private:
    int value;
public:
    IntegerWrapper(int v = 10) : value(v) {}
    IntegerWrapper& operator=(const IntegerWrapper& other) {
        if(this != &other) value = other.value;
        return *this;
    }
    int getValue() const { return value; }
};

class ComplexDate {
private:
    int year;
    int month;
    int day;
    IntegerWrapper flag;
public:
    ComplexDate(int y=2005, int m=5, int d=25) : year(y), month(m), day(d), flag(0) {}
    void printDate() {
        std::cout << year << "-" << month << "-" << day << " (flag: " << flag.getValue() << ")" << std::endl;
    }
};

int main() {
    ComplexDate today(2024,7,12);
    ComplexDate tomorrow;
    tomorrow = today;
    tomorrow.printDate();
    return 0;
}

Here the default assignment overload for ComplexDate will automatically call IntegerWrapper::operator= for the flag member.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.