Like function overloading, C++ also support a powerful concept called operator overloading. C++ contains a rich set of operators such as +,-, *, >>, <,++ etc., which work on built-in types such as int, float, char to perform arithmetic, relational, logical and various other operations. Let us consider the statement c = a+b;. Here, the + operator can be used to perform add operation on operands a and b, either int, float, double types, etc. Thus, the + operator performs multiple tasks as it can perform addition of any of the built-in types and hence already overloaded. The compiler has prior knowledge about how to perform this add operation on built-in types. However, if a, b, c are variables (objects) of a user-defined type (class), then the statement c=a+b; will not work as the compiler doesn’t understand how to add variables (objects) of a user-defined type (class).
The compiler will give us an error if the addition of two objects is carried out. To make the above operation work for the variables of the user-defined type, we need to explicitly overload the + operator by making a function to perform the desired task. This property of giving additional meaning to existing operators to work with variables of user-defined types is called operator overloading. Providing additional meaning to an existing operator by overloading it doesn’t change the operator’s original meaning but extends its functionality.
The main advantages of operator overloading are:
It makes the program more readable and easier to understand by allowing manipulation of objects of class’s type to behave in the same manner as that of variables of built-in types.
For example: if we want to add two complex numbers, then we need to define member function add() and call it using the following notation.
c3.add (c1, c2) ; or c3 = c1.add(c2) ;
The above statements are obscure as the programmer needs to remember the function’s name and how to call it. The problem worsens even more when the program becomes large and complex.
A better alternative for performing the addition of two complex numbers is to overload the ‘+’ operator. So the above statements can be written in an easily readable form as
c3 = c1+c2;
This notation is often clearer and more familiar to programmers.
• Operator overloading is useful in large and complex programs involving multiple objects of different classes. It provides a common interface corresponding to an operator that performs similar operations on various objects. For example: In a program, the + operator can add two-time class objects, two date class objects etc.
time1 = time2 + time3; date1 = date2 + date3;
• Operator overloading enhances the capability of C++ language by extending the functionality of most of the existing operators as per your requirement and thus contribute to C++’s extensibility.
• Debugging becomes easier as the code becomes more understandable and clearer.
• Operator overloading helps to gain greater control over objects’ behaviour in your program (i.e. controlling the lifetime of objects). For example, – You can overload memory allocation and deallocation functions for your classes to specify exactly how memory should be distributed and reclaimed for each new object.
We’ll be covering the following topics in this tutorial:
Syntax for Operator Overloading
To give additional meaning to an operator, we need to overload it by creating an operator function. An operator function defines the operation that the overloaded operator will perform when used with a relative class’s objects. An operator function can be a member function of the class or a non-member function (friend function). However, the way of making a member operator function and a non-member operator function is different. We will first discuss the operator function as a member function, which is declared in the class’s public part.
The syntax for defining an operator function which is a member function of the class, is as follows
a) When declared inside and defined outside the class
return_ type classname::operator# (argument_list) { .............. /body of function/ .............. }
b) When defined inside the class
return_type operator#(argument_list){....}
Here, return_type is the type of value to be returned. If the operator function returns an object of the class it operates on, then the return_type is the class’s name. When operator function is defined outside the class, the scope resolution operator specifies the class to which it belongs. The keyword operator is used to overload an operator #. One of the C++ operators is to be overloaded. The compiler distinguishes an operator function from an ordinary member function of the class by the keyword operator. The name of the operator function is operator#.
So to overload, the + operator, the # symbol is replaced by +. The argument_list is the number of arguments passed to operator function. The number of arguments in the argument_Iist depends on whether the operator will be overloaded unary or binary operator. If the operator is unary, then the argument_list is empty, and if it is a binary operator, then the argument_list contains one parameter.
The operators that can be overloaded are:
The operators that cannot be overloaded are:
Some valid examples of declaring overloaded operator function in the class are as follows :
• complex operator +(complex);
• void operator ++();
• int operator >(distance);
• string operator ==(string);
• void operator +=(complex);
• void operator delete (void *);
Rules for Operator Overloading
Operator overloading provides additional meaning to existing C++ operators, thus contributing to C++ extensibility. While overloading an operator, specific rules need to be followed, which are as follows.
• New operators cannot be created for overloading: Only the existing C++ operators can be overloaded. It is not possible to create a new operator for overloading. For example, we cannot define a new operator ** to calculate the exponential values used in some other languages.
The advantages of the above guideline are that it simplifies the compiler’s work as compiler need not worry about its precedence and associativity of the new operator which is to be added.
• All operators cannot be overloaded: Most C++ operators can be overloaded. However, certain operators such as .(member access operator), :: (scope resolution operator), size rename of, ?: (conditional operator), .* (Pointer to the member) cannot be overloaded.
The main reason why these operators cannot be overloaded is that most of them already work with objects for performing specific tasks.
• Retain Meaning: While overloading an operator, it should be kept in mind that operators should retain its natural meaning. In other words, the operation performed by overloading an operator should be similar to those defined for built-in types. For example, we can overload plus (+) operator to perform subtraction. But it decreases the program’s readability as it does not perform the task it was built for.
• Retain Syntax: The syntactic properties of the operator cannot be changed by overloading. In other words, we can’t change the number of operands associated with the operator by overloading. It means unary operator always work on only one operand, and a binary operator always works on two operands. For example, Binary arithmetic assignment operator (+=) can work only on two operands and not as a unary operator. So, the statement c+=; is invalid. Similarly, the unary logical NOT (!) operator cannot be overloaded as a binary operator for comparing two string objects.
• Retains hierarchy of operators: The operators’ precedence and associativity can’t be changed by overloading. These rules determine the order in which operators are evaluated in the statement. However, we can use parentheses to change the order of evaluation. This rule’s main advantage is that it makes the program code readable and understandable, even if user-defined operands are involved in operator expression. Moreover, compiler requirements are also simplified. For example, The statement a == b – c; always evaluate the arithmetic operator – before == as per the precedence rule.
• Use appropriate function names when required: Avoid overloading the operators to perform illogical operations. Suppose an operation’s meaning to be performed by overloading an operator is unpredictable or doubtful to the user. In that case, it is advisable to use a function with an appropriate name instead of an overloaded operator function since the function name can state its purpose. For example: If you want to extract the part of a string, then it is better to create a function with name substring() instead of overloading some operator -. So before overloading an operator, we should have sufficient reason to do so.
• Parameters in overloaded operation function: Overloaded operator function must either be a member function of the class or a non-member function (friend function). If the overloaded operator function is a member function, then overloaded unary operator takes no parameter, and a binary operator takes one parameter. However, if the overloaded operator function is non-member function, then overloaded unary operator takes one parameter, and a binary operator takes two parameters.
• Dual meanings of an operator can be overloaded: If an operator can be used either as a unary operator or a binary operator (+, -, */), either one or both of these operators can be overloaded. For example, The operator * can be used for multiplication (binary operator) or dereferencing a pointer (unary operator). You can overload both meanings of the operators by defining separate operator functions within a program.
• Predefined operations are not automatically applied to classes: The operations predefined for primitive data types are not automatically transferred to user-defined data types. For example, Overloading of the assignment (=) and subtraction (-) operator doesn’t imply that (-=) operator has been automatically overloaded. We need to overload (-=) operator explicitly so the default property c1 = c1- c2 is equivalent to c1 -= c2, which applies to primitive data types doesn’t automatically apply to classes because operators are implemented differently.
• Avoid ambiguity: The presence of more than one conversion function performing the same operation is not allowed as it puts the compiler in an uncertain situation. The compiler can’t select appropriate conversion function in these situations. For instance, to perform conversions between objects of different classes, if we write both 1-argument constructor in the destination objects class and operator function in the source object’s class for performing the same conversions, then it confuses the compiler as it does not know which one to select and generates an error message. So one should always avoid defining multiple conversion functions for performing the same operations.
• Overloaded operator cannot have default arguments except for function call operator.
• All overloaded operators are inherited by derived classes except assignment operator (=).
• The operators [ ], ( ), -> or = must be overloaded as a member of the class and not using friend function.