Overloading Arithmetic Assignment Operator In C++

C++ Operator Overloading Guidelines

One of the nice features of C++ is that you can give special meanings to operators, when they are used with user-defined classes. This is called operator overloading. You can implement C++ operator overloads by providing special member-functions on your classes that follow a particular naming convention. For example, to overload the operator for your class, you would provide a member-function named on your class.

The following set of operators is commonly overloaded for user-defined classes:

  • (assignment operator)
  • (binary arithmetic operators)
  • (compound assignment operators)
  • (comparison operators)

Here are some guidelines for implementing these operators. These guidelines are very important to follow, so definitely get in the habit early.

Assignment Operator

The assignment operator has a signature like this:

class MyClass { public: ... MyClass & operator=(const MyClass &rhs); ... } MyClass a, b; ... b = a; // Same as b.operator=(a);

Notice that the operator takes a const-reference to the right hand side of the assignment. The reason for this should be obvious, since we don't want to change that value; we only want to change what's on the left hand side.

Also, you will notice that a reference is returned by the assignment operator. This is to allow operator chaining. You typically see it with primitive types, like this:

int a, b, c, d, e; a = b = c = d = e = 42; This is interpreted by the compiler as: a = (b = (c = (d = (e = 42)))); In other words, assignment is right-associative. The last assignment operation is evaluated first, and is propagated leftward through the series of assignments. Specifically:
  • assigns 42 to , then returns as the result
  • The value of is then assigned to , and then is returned as the result
  • The value of is then assigned to , and then is returned as the result
  • etc.

Now, in order to support operator chaining, the assignment operator must return some value. The value that should be returned is a reference to the left-hand side of the assignment.

Notice that the returned reference is not declared . This can be a bit confusing, because it allows you to write crazy stuff like this:

MyClass a, b, c; ... (a = b) = c; // What?? At first glance, you might want to prevent situations like this, by having return a reference. However, statements like this will work with primitive types. And, even worse, some tools actually rely on this behavior. Therefore, it is important to return a non- reference from your . The rule of thumb is, "If it's good enough for s, it's good enough for user-defined data-types."

So, for the hypothetical assignment operator, you would do something like this:

// Take a const-reference to the right-hand side of the assignment. // Return a non-const reference to the left-hand side. MyClass& MyClass::operator=(const MyClass &rhs) { ... // Do the assignment operation! return *this; // Return a reference to myself. } Remember, is a pointer to the object that the member function is being called on. Since is treated as , you can see why it makes sense to return the object that the function is called on; object is the left-hand side.

But, the member function needs to return a reference to the object, not a pointer to the object. So, it returns , which returns what points at (i.e. the object), not the pointer itself. (In C++, instances are turned into references, and vice versa, pretty much automatically, so even though is an instance, C++ implicitly converts it into a reference to the instance.)

Now, one more very important point about the assignment operator:


This is especially important when your class does its own memory allocation. Here is why: The typical sequence of operations within an assignment operator is usually something like this:

MyClass& MyClass::operator=(const MyClass &rhs) { // 1. Deallocate any memory that MyClass is using internally // 2. Allocate some memory to hold the contents of rhs // 3. Copy the values from rhs into this instance // 4. Return *this } Now, what happens when you do something like this: MyClass mc; ... mc = mc; // BLAMMO. You can hopefully see that this would wreak havoc on your program. Because is on the left-hand side and on the right-hand side, the first thing that happens is that releases any memory it holds internally. But, this is where the values were going to be copied from, since is also on the right-hand side! So, you can see that this completely messes up the rest of the assignment operator's internals.

The easy way to avoid this is to CHECK FOR SELF-ASSIGNMENT. There are many ways to answer the question, "Are these two instances the same?" But, for our purposes, just compare the two objects' addresses. If they are the same, then don't do assignment. If they are different, then do the assignment.

So, the correct and safe version of the assignment operator would be this:

MyClass& MyClass::operator=(const MyClass &rhs) { // Check for self-assignment! if (this == &rhs) // Same object? return *this; // Yes, so skip assignment, and just return *this. ... // Deallocate, allocate new space, copy values... return *this; } Or, you can simplify this a bit by doing: MyClass& MyClass::operator=(const MyClass &rhs) { // Only do assignment if RHS is a different object from this. if (this != &rhs) { ... // Deallocate, allocate new space, copy values... } return *this; } Remember that in the comparison, is a pointer to the object being called, and is a pointer to the object being passed in as the argument. So, you can see that we avoid the dangers of self-assignment with this check.

In summary, the guidelines for the assignment operator are:

  1. Take a const-reference for the argument (the right-hand side of the assignment).
  2. Return a reference to the left-hand side, to support safe and reasonable operator chaining. (Do this by returning .)
  3. Check for self-assignment, by comparing the pointers ( to ).

Compound Assignment Operators

I discuss these before the arithmetic operators for a very specific reason, but we will get to that in a moment. The important point is that these are destructive operators, because they update or replace the values on the left-hand side of the assignment. So, you write:

MyClass a, b; ... a += b; // Same as a.operator+=(b) In this case, the values within are modified by the operator.

How those values are modified isn't very important - obviously, what represents will dictate what these operators mean.

The member function signature for such an operator should be like this:

MyClass & MyClass::operator+=(const MyClass &rhs) { ... } We have already covered the reason why is a const-reference. And, the implementation of such an operation should also be straightforward.

But, you will notice that the operator returns a -reference, and a non-const one at that. This is so you can do things like this:

MyClass mc; ... (mc += 5) += 3;

Don't ask me why somebody would want to do this, but just like the normal assignment operator, this is allowed by the primitive data types. Our user-defined datatypes should match the same general characteristics of the primitive data types when it comes to operators, to make sure that everything works as expected.

This is very straightforward to do. Just write your compound assignment operator implementation, and return at the end, just like for the regular assignment operator. So, you would end up with something like this:

MyClass & MyClass::operator+=(const MyClass &rhs) { ... // Do the compound assignment work. return *this; }

As one last note, in general you should beware of self-assignment with compound assignment operators as well. Fortunately, none of the C++ track's labs require you to worry about this, but you should always give it some thought when you are working on your own classes.

Binary Arithmetic Operators

The binary arithmetic operators are interesting because they don't modify either operand - they actually return a new value from the two arguments. You might think this is going to be an annoying bit of extra work, but here is the secret:

Define your binary arithmetic operators using your compound assignment operators.

There, I just saved you a bunch of time on your homeworks.

So, you have implemented your operator, and now you want to implement the operator. The function signature should be like this:

// Add this instance's value to other, and return a new instance // with the result. const MyClass MyClass::operator+(const MyClass &other) const { MyClass result = *this; // Make a copy of myself. Same as MyClass result(*this); result += other; // Use += to add other to the copy. return result; // All done! } Simple!

Actually, this explicitly spells out all of the steps, and if you want, you can combine them all into a single statement, like so:

// Add this instance's value to other, and return a new instance // with the result. const MyClass MyClass::operator+(const MyClass &other) const { return MyClass(*this) += other; } This creates an unnamed instance of , which is a copy of . Then, the operator is called on the temporary value, and then returns it.

If that last statement doesn't make sense to you yet, then stick with the other way, which spells out all of the steps. But, if you understand exactly what is going on, then you can use that approach.

You will notice that the operator returns a instance, not a reference. This is so that people can't write strange statements like this:

MyClass a, b, c; ... (a + b) = c; // Wuh...? This statement would basically do nothing, but if the operator returns a non- value, it will compile! So, we want to return a instance, so that such madness will not even be allowed to compile.

To summarize, the guidelines for the binary arithmetic operators are:

  1. Implement the compound assignment operators from scratch, and then define the binary arithmetic operators in terms of the corresponding compound assignment operators.
  2. Return a instance, to prevent worthless and confusing assignment operations that shouldn't be allowed.

Comparison Operators and

The comparison operators are very simple. Define first, using a function signature like this:

bool MyClass::operator==(const MyClass &other) const { ... // Compare the values, and return a bool result. } The internals are very obvious and straightforward, and the return-value is also very obvious.

The important point here is that the operator can also be defined in terms of the operator, and you should do this to save effort. You can do something like this:

bool MyClass::operator!=(const MyClass &other) const { return !(*this == other); } That way you get to reuse the hard work you did on implementing your operator. Also, your code is far less likely to exhibit inconsistencies between and , since one is implemented in terms of the other.
Updated Oct 23, 2007
Copyright (c) 2005-2007, California Institute of Technology.

Operator overloading[edit]

Operator overloading (less commonly known as ad-hoc polymorphism) is a specific case of polymorphism (part of the OO nature of the language) in which some or all operators like , or are treated as polymorphic functions and as such have different behaviors depending on the types of its arguments. Operator overloading is usually only syntactic sugar. It can easily be emulated using function calls.

Consider this operation:

Using operator overloading permits a more concise way of writing it, like this:

a + b * c

(Assuming the operator has higher precedence than .)

Operator overloading can provide more than an aesthetic benefit, since the language allows operators to be invoked implicitly in some circumstances. Problems, and critics, to the use of operator overloading arise because it allows programmers to give operators completely free functionality, without an imposition of coherency that permits to consistently satisfy user/reader expectations. Usage of the operator is an example of this problem.

// The expressiona<<1;

Will return twice the value of if is an integer variable, but if is an output stream instead this will write "1" to it. Because operator overloading allows the programmer to change the usual semantics of an operator, it is usually considered good practice to use operator overloading with care.

To overload an operator is to provide it with a new meaning for user-defined types. This is done in the same fashion as defining a function. The basic syntax follows (where @ represents a valid operator):

return_typeoperator@(argument_list){// ... definition}

Not all operators may be overloaded, new operators cannot be created, and the precedence, associativity or arity of operators cannot be changed (for example ! cannot be overloaded as a binary operator). Most operators may be overloaded as either a member function or non-member function, some, however, must be defined as member functions. Operators should only be overloaded where their use would be natural and unambiguous, and they should perform as expected. For example, overloading + to add two complex numbers is a good use, whereas overloading * to push an object onto a vector would not be considered good style.

Operator overloading should only be utilized when the meaning of the overloaded operator's operation is unambiguous and practical for the underlying type and where it would offer a significant notational brevity over appropriately named function calls.

A simple Message Header
// sample of Operator Overloading#include<string>classPlMessageHeader{std::stringm_ThreadSender;std::stringm_ThreadReceiver;//return true if the messages are equal, false otherwiseinlinebooloperator==(constPlMessageHeader&b)const{return((b.m_ThreadSender==m_ThreadSender)&&(b.m_ThreadReceiver==m_ThreadReceiver));}//return true if the message is for nameinlineboolisFor(conststd::string&name)const{return(m_ThreadReceiver==name);}//return true if the message is for nameinlineboolisFor(constchar*name)const{return(m_ThreadReceiver==name);// since name type is std::string, it becomes unsafe if name == NULL}};

The use of the keyword in the example above is technically redundant, as functions defined within a class definition like this are implicitly inline.

Operators as member functions[edit]

Aside from the operators which must be members, operators may be overloaded as member or non-member functions. The choice of whether or not to overload as a member is up to the programmer. Operators are generally overloaded as members when they:

  1. change the left-hand operand, or
  2. require direct access to the non-public parts of an object.

When an operator is defined as a member, the number of explicit parameters is reduced by one, as the calling object is implicitly supplied as an operand. Thus, binary operators take one explicit parameter and unary operators none. In the case of binary operators, the left hand operand is the calling object, and no type coercion will be done upon it. This is in contrast to non-member operators, where the left hand operand may be coerced.

// binary operator as member function//Vector2D Vector2D::operator+(const Vector2D& right)const [...]// binary operator as non-member function//Vector2D operator+(const Vector2D& left, const Vector2D& right)[...]// binary operator as non-member function with 2 arguments //friend Vector2D operator+(const Vector2D& left, const Vector2D& right) [...]// unary operator as member function//Vector2D Vector2D::operator-()const {...}// unary operator as non-member function[...]//Vector2D operator-(const Vector2D& vec) [...]

Overloadable operators[edit]

Arithmetic operators[edit]
  • + (addition)
  • - (subtraction)
  • * (multiplication)
  • / (division)
  • % (modulus)

As binary operators, these involve two arguments which do not have to be the same type. These operators may be defined as member or non-member functions. An example illustrating overloading for the addition of a 2D mathematical vector type follows.


It is good style to only overload these operators to perform their customary arithmetic operation. Because operator has been overloaded as member function, it can access private fields.

Bitwise operators[edit]
  • ^ (XOR)
  • | (OR)
  • & (AND)
  • ~ (complement)
  • << (shift left, insertion to stream)
  • >> (shift right, extraction from stream)

All of the bitwise operators are binary, except complement, which is unary. It should be noted that these operators have a lower precedence than the arithmetic operators, so if ^ were to be overloaded for exponentiation, x ^ y + z may not work as intended. Of special mention are the shift operators, << and >>. These have been overloaded in the standard library for interaction with streams. When overloading these operators to work with streams the rules below should be followed:

  1. overload << and >> as friends (so that it can access the private variables with the stream be passed in by references
  2. (input/output modifies the stream, and copying is not allowed)
  3. the operator should return a reference to the stream it receives (to allow chaining, cout << 3 << 4 << 5)
An example using a 2D vector
friendostream&operator<<(ostream&out,constVector2D&vec)// output{out<<"("<<vec.x()<<", "<<vec.y()<<")";returnout;}friendistream&operator>>(istream&in,Vector2D&vec)// input{doublex,y;// skip opening paranthesisin.ignore(1);// read xin>>x;vec.set_x(x);// skip delimiterin.ignore(2);// read yin>>y;vec.set_y(y);// skip closing paranthesisin.ignore(1);returnin;}
Assignment operator[edit]

The assignment operator, =, must be a member function, and is given default behavior for user-defined classes by the compiler, performing an assignment of every member using its assignment operator. This behavior is generally acceptable for simple classes which only contain variables. However, where a class contains references or pointers to outside resources, the assignment operator should be overloaded (as general rule, whenever a destructor and copy constructor are needed so is the assignment operator), otherwise, for example, two strings would share the same buffer and changing one would change the other.

In this case, an assignment operator should perform two duties:

  1. clean up the old contents of the object
  2. copy the resources of the other object

For classes which contain raw pointers, before doing the assignment, the assignment operator should check for self-assignment, which generally will not work (as when the old contents of the object are erased, they cannot be copied to refill the object). Self assignment is generally a sign of a coding error, and thus for classes without raw pointers, this check is often omitted, as while the action is wasteful of cpu cycles, it has no other effect on the code.

classBuggyRawPointer{// example of super-common mistakeT*m_ptr;public:BuggyRawPointer(T*ptr):m_ptr(ptr){}BuggyRawPointer&operator=(BuggyRawPointerconst&rhs){deletem_ptr;// free resource; // Problem here!m_ptr=0;m_ptr=rhs.m_ptr;return*this;};};BuggyRawPointerx(newT);x=x;// We might expect this to keep x the same. This sets x.m_ptr == 0. Oops!// The above problem can be fixed like so:classWithRawPointer2{T*m_ptr;public:WithRawPointer2(T*ptr):m_ptr(ptr){}WithRawPointer2&operator=(WithRawPointer2const&rhs){if(this!=&rhs){deletem_ptr;// free resource;m_ptr=0;m_ptr=rhs.m_ptr;}return*this;};};WithRawPointer2x2(newT);x2=x2;// x2.m_ptr unchanged.

Another common use of overloading the assignment operator is to declare the overload in the private part of the class and not define it. Thus any code which attempts to do an assignment will fail on two accounts, first by referencing a private member function and second fail to link by not having a valid definition. This is done for classes where copying is to be prevented, and generally done with the addition of a privately declared copy constructor

classDoNotCopyOrAssign{public:DoNotCopyOrAssign(){};private:DoNotCopyOrAssign(DoNotCopyOrAssignconst&);DoNotCopyOrAssign&operator=(DoNotCopyOrAssignconst&);};classMyClass:publicDoNotCopyOrAssign{public:MyClass();};MyClassx,y;x=y;// Fails to compile due to private assignment operator;MyClassz(x);// Fails to compile due to private copy constructor.
Relational operators[edit]
  • == (equality)
  • != (inequality)
  • > (greater-than)
  • < (less-than)
  • >= (greater-than-or-equal-to)
  • <= (less-than-or-equal-to)

All relational operators are binary, and should return either true or false. Generally, all six operators can be based off a comparison function, or each other, although this is never done automatically (e.g. overloading > will not automatically overload < to give the opposite). There are, however, some templates defined in the header <utility>; if this header is included, then it suffices to just overload operator== and operator<, and the other operators will be provided by the STL.

Logical operators[edit]
  • ! (NOT)
  • && (AND)
  • || (OR)

The logical operators AND are used when evaluating two expressions to obtain a single relational result.The operator corresponds to the boolean logical operation AND,which yields true if operands are true,and false otherwise.The following panel shows the result of operator evaluating the expression.

The ! operator is unary, && and || are binary. It should be noted that in normal use, && and || have "short-circuit" behavior, where the right operand may not be evaluated, depending on the left operand. When overloaded, these operators get function call precedence, and this short circuit behavior is lost. It is best to leave these operators alone.


If the result of Function1() is false, then Function2() is not called.


Both Function3() and Function4() will be called no matter what the result of the call is to Function3() This is a waste of CPU processing, and worse, it could have surprising unintended consequences compared to the expected "short-circuit" behavior of the default operators. Consider:

externMyObject*ObjectPointer;boolFunction1(){returnObjectPointer!=null;}boolFunction2(){returnObjectPointer->MyMethod();}MyBoolFunction3(){returnObjectPointer!=null;}MyBoolFunction4(){returnObjectPointer->MyMethod();}booloperator&&(MyBoolconst&,MyBoolconst&);Function1()&&Function2();// Does not execute Function2() when pointer is nullFunction3()&&Function4();// Executes Function4() when pointer is null
Compound assignment operators[edit]
  • += (addition-assignment)
  • -= (subtraction-assignment)
  • *= (multiplication-assignment)
  • /= (division-assignment)
  • %= (modulus-assignment)
  • &= (AND-assignment)
  • |= (OR-assignment)
  • ^= (XOR-assignment)
  • <<= (shift-left-assignment)
  • >>= (shift-right-assignment)

Compound assignment operators should be overloaded as member functions, as they change the left-hand operand. Like all other operators (except basic assignment), compound assignment operators must be explicitly defined, they will not be automatically (e.g. overloading = and + will not automatically overload +=). A compound assignment operator should work as expected: A @= B should be equivalent to A = A @ B. An example of += for a two-dimensional mathematical vector type follows.

Increment and decrement operators[edit]
  • ++ (increment)
  • -- (decrement)

Increment and decrement have two forms, prefix (++i) and postfix (i++). To differentiate, the postfix version takes a dummy integer. Increment and decrement operators are most often member functions, as they generally need access to the private member data in the class. The prefix version in general should return a reference to the changed object. The postfix version should just return a copy of the original value. In a perfect world, A += 1, A = A + 1, A++, ++A should all leave A with the same value.

SomeValue&SomeValue::operator++()// prefix{++data;return*this;}SomeValueSomeValue::operator++(intunused)// postfix{SomeValueresult=*this;++data;returnresult;}

Often one operator is defined in terms of the other for ease in maintenance, especially if the function call is complex.

SomeValueSomeValue::operator++(intunused)// postfix{SomeValueresult=*this;++(*this);// call SomeValue::operator++()returnresult;}
Subscript operator[edit]

The subscript operator, [ ], is a binary operator which must be a member function (hence it takes only one explicit parameter, the index). The subscript operator is not limited to taking an integral index. For instance, the index for the subscript operator for the std::map template is the same as the type of the key, so it may be a string etc. The subscript operator is generally overloaded twice; as a non-constant function (for when elements are altered), and as a constant function (for when elements are only accessed).

Function call operator[edit]

The function call operator, ( ), is generally overloaded to create objects which behave like functions, or for classes that have a primary operation. The function call operator must be a member function, but has no other restrictions - it may be overloaded with any number of parameters of any type, and may return any type. A class may also have several definitions for the function call operator.

Address of, Reference, and Pointer operators[edit]

These three operators, operator&(), operator*() and operator->() can be overloaded. In general these operators are only overloaded for smart pointers, or classes which attempt to mimic the behavior of a raw pointer. The pointer operator, operator->() has the additional requirement that the result of the call to that operator, must return a pointer, or a class with an overloaded operator->(). In general A == *&A should be true.

Note that overloading operator& invokes undefined behavior:

ISO/IEC 14882:2003, Section 5.3.1
The address of an object of incomplete type can be taken, but if the complete type of that object is a class type that declares operator&() as a member function, then the behavior is undefined (and no diagnostic is required).
classT{public:constmemberFunction()const;};// forward declarationclassDullSmartReference;classDullSmartPointer{private:T*m_ptr;public:DullSmartPointer(T*rhs):m_ptr(rhs){};DullSmartReferenceoperator*()const{returnDullSmartReference(*m_ptr);}T*operator->()const{returnm_ptr;}};classDullSmartReference{private:T*m_ptr;public:DullSmartReference(T&rhs):m_ptr(&rhs){}DullSmartPointeroperator&()const{returnDullSmartPointer(m_ptr);}// conversion operatoroperatorT(){return*m_ptr;}};DullSmartPointerdsp(newT);dsp->memberFunction();// calls T::memberFunctionTt;DullSmartReferencedsr(t);dsp=&dsr;t=dsr;// calls the conversion operator

These are extremely simplified examples designed to show how the operators can be overloaded and not the full details of a SmartPointer or SmartReference class. In general you won't want to overload all three of these operators in the same class.

Comma operator[edit]

The comma operator,() , can be overloaded. The language comma operator has left to right precedence, the operator,() has function call precedence, so be aware that overloading the comma operator has many pitfalls.


For non overloaded comma operator, the order of execution will be Function1(), Function2(); With the overloaded comma operator, the compiler can call either Function1(), or Function2() first.

Member Reference operators[edit]

The two member access operators, operator->() and operator->*() can be overloaded. The most common use of overloading these operators is with defining expression template classes, which is not a common programming technique. Clearly by overloading these operators you can create some very unmaintainable code so overload these operators only with great care.

When the -> operator is applied to a pointer value of type (T *), the language dereferences the pointer and applies the . member access operator (so x->m is equivalent to (*x).m). However, when the -> operator is applied to a class instance, it is called as a unary postfix operator; it is expected to return a value to which the -> operator can again be applied. Typically, this will be a value of type (T *), as in the example under Address of, Reference, and Pointer operators above, but can also be a class instance with operator->() defined; the language will call operator->() as many times as necessary until it arrives at a value of type (T *).

Memory management operators[edit]
  • new (allocate memory for object)
  • new[ ] (allocate memory for array)
  • delete (deallocate memory for object)
  • delete[ ] (deallocate memory for array)

The memory management operators can be overloaded to customize allocation and deallocation (e.g. to insert pertinent memory headers). They should behave as expected, new should return a pointer to a newly allocated object on the heap, delete should deallocate memory, ignoring a NULL argument. To overload new, several rules must be followed:

  • new must be a member function
  • the return type must be void*
  • the first explicit parameter must be a size_t value

To overload delete there are also conditions:

  • delete must be a member function (and cannot be virtual)
  • the return type must be void
  • there are only two forms available for the parameter list, and only one of the forms may appear in a class:
Conversion operators[edit]

Conversion operators enable objects of a class to be either implicitly (coercion) or explicitly (casting) converted to another type. Conversion operators must be member functions, and should not change the object which is being converted, so should be flagged as constant functions. The basic syntax of a conversion operator declaration, and declaration for an int-conversion operator follows.

operator''type''()const;// const is not necessary, but is good styleoperatorint()const;

Notice that the function is declared without a return-type, which can easily be inferred from the type of conversion. Including the return type in the function header for a conversion operator is a syntax error.

doubleoperatordouble()const;// error - return type included

Operators which cannot be overloaded[edit]

  • ?: (conditional)
  • . (member selection)
  • .* (member selection with pointer-to-member)
  • :: (scope resolution)
  • (object size information)
  • typeid (object type information)

To understand the reasons why the language doesn't permit these operators to be overloaded, read "Why can't I overload dot, ::, , etc.?" at the Bjarne Stroustrup's C++ Style and Technique FAQ ( http://www.stroustrup.com/bs_faq2.html#overload-dot ).

0 Thoughts to “Overloading Arithmetic Assignment Operator In C++

Leave a comment

L'indirizzo email non verrĂ  pubblicato. I campi obbligatori sono contrassegnati *