In any science, there are standard notations that facilitate the understanding of ideas. For example, in mathematics, this is multiplication, division, addition, and other symbolic notation. The expression (x + y * z) is much easier to understand than "multiply y, c, z and add to x". Imagine, until the XVI century, mathematics did not have symbolic designations, all expressions were written verbally as if it were an artistic text with a description. And the usual designations for operations appeared even later. It is difficult to overestimate the meaning of a brief symbolic notation. Based on these considerations, operator overloads have been added to programming languages. Let's look at an example.
Operator Overload Example
Like almost any language, C ++ supports many operators that work with data types built into the language standard. But most programs use custom types to solve problems. For example, complex mathematics or matrix algebra are implemented in a program by representing complex numbers or matrices as custom C ++ types. Built-in operators do not know how to distribute their work and perform the necessary procedures on user classes, no matter how obvious they may seem. Therefore, to add matrices, for example, a separate function is usually created. Obviously, calling the function sum_matrix (A, B) in the code will be less clear than the expression A + B.
Consider an example class of complex numbers:
Similarly, you can do, for example, overloading I / O statements in C ++ and adapt them to output such complex structures as matrices.
Overload Operators
A complete list of all operators for which the overload mechanism can be used:
+ | - | * | / | % | ^ | & |
| | ~ | ! | = | < | > | + = |
- = | * = | / = | % = | ^ = | & = | | = |
<< | >> | >> = | << = | == | ! = | <= |
> = | && | || | ++ | - | -> * | , |
-> | [] | () | new | new [] | delete | delete [] |
As the table shows, overloading is acceptable for most language operators. There is no need for operator overload. This is for convenience only. Therefore, operator overloading in Java, for example, is absent. And now about the next important point.
Forbidden operators
- Scope Resolution - “::”;
- Member choice - “.”;
- Member selection through a member pointer - “. *”;
- The ternary conditional operator is “?:”;
- Sizeof operator
- Typeid statement
The right operand of these operators is the name, not the value. Therefore, the resolution of their overload could lead to the writing of many ambiguous constructions and would greatly complicate the life of programmers. Although there are many programming languages that allow overloading of all statements - for example, overloading Python statements.
Limitations
Operator Overload Limitations:

- You cannot change the binary operator to unary and vice versa, just like you cannot add a third operand.
- You cannot create new operators besides those that are available. This limitation helps to eliminate many ambiguities. If there is a need for a new operator, you can use for these purposes a function that will perform the required action.
- An operator function can either be a member of a class or have at least one user-type argument. The exceptions are the new and delete operators. Such a rule prohibits changing the meaning of expressions if they do not contain objects of types defined by the user. In particular, you cannot create an operator function that works exclusively with pointers or make the addition operator work like multiplication. The exceptions are the operators "=", "&" and "," for class objects.
- An operator function with the first member belonging to one of the built-in C ++ data types cannot be a member of a class.
- The name of any operator function begins with the operator keyword, followed by the symbolic designation of the operator itself.
- Built-in operators are defined in such a way that there is a connection between them. For example, the following operators are equivalent to each other: ++ x; x + = 1; x = x + 1. After redefinition, the connection between them will not be saved. The programmer will have to take care of maintaining their collaboration in a similar way with new types.
- The compiler does not know how to think. The expressions z + 5 and 5 + z (where z is a complex number) will be treated differently by the compiler. The first is “complex + number”, and the second is “number + complex”. Therefore, for each expression, you need to define your own addition operator.
- When looking for an operator definition, the compiler does not give preference to the member functions of the class nor auxiliary functions that are defined outside the class. For the compiler, they are equal.
Interpretations of binary and unary operators.
A binary operator is defined as a member function with one variable or as a function with two variables. For any binary operator @ in the expression a @ b, @, the following constructions are valid:
a.operator @ (b) or operator @ (a, b).
Consider the example of a class of complex numbers as the definition of operations as members of the class and auxiliary.
class complex { double re, im; public: complex& operator+=(complex z); complex& operator*=(complex z); };
Which operator will be selected, and whether it will be selected at all, is determined by the internal mechanisms of the language, which will be discussed below. This usually happens by type matching.
The choice to describe a function as a member of the class or outside it is, in general, a matter of taste. In the example above, the selection principle was as follows: if the operation modifies the left operand (for example, a + = b), then write it inside the class and use the variable transfer to the address to change it directly; if the operation does not change anything and simply returns a new value (for example, a + b), move it outside the class definition.
The definition of overloading unary operators in C ++ occurs in a similar way, with the difference that they are divided into two types:
- the prefix operator located before the operand is @a, for example, ++ i. o is defined as a.operator @ () or operator @ (aa);
- the postfix operator located after the operand is b @, for example, i ++. o is defined as b.operator @ (int) or operator @ (b, int)
In the same way as with binary operators for the case when the operator declaration is both in the class and outside the class, the choice will be made by C ++ mechanisms.
Rules for choosing an operator
Let the binary operator @ be applied to objects x from class X and y from class Y. The rules for resolving x @ y will be as follows:
- if X is a class, look inside it for the definition of operator @ as a member of X, or the base class of X;
- View the context in which x @ y is located
- if X belongs to the namespace N, look for the operator declaration in N;
- if Y belongs to the namespace M, look for the operator declaration in M.
In the event that several declarations of the operator @ operator were found in 1-4, the choice will be made according to the rules for resolving overloaded functions.
The search for unary operator declarations occurs in exactly the same way.
Refined complex class definition
Now we will construct the class of complex numbers in a more detailed way in order to demonstrate a number of previously announced rules.
class complex { double re, im; public: complex& operator+=(complex z) {
As you can see from the code, operator overloading has a very complex mechanism, which can grow greatly. However, such a detailed approach allows overloading even for very complex data structures. For example, overloading C ++ statements in a template class.
Creating such functions for everyone and everything can be tedious and lead to errors. For example, if you add a third type to the functions considered, you will need to consider operations for reasons of combining the three types. You will have to write 3 functions with one argument, 9 with two and 27 with three. Therefore, in some cases, the implementation of all these functions and a significant reduction in their number can be achieved through the use of type conversion.
Special operators
The indexing operator “[]” should always be defined as a member of the class, since it reduces the behavior of an object to an array. The indexing argument can be of any type, which allows you to create, for example, associative arrays.
The operator of calling the function “()” can be considered as a binary operation. For example, in the construction “expression (list of expressions)”, the left operand of the binary operation () will be “expression”, and the right one is the list of expressions. The operator () () function must be a member of the class.
The sequence operator “,” (comma) is called for objects if there is a comma next to them. However, the operator is not involved in enumerating the arguments of the function.
The dereference operator “->” must also be defined as a member of the function. By its meaning, it can be defined as a unary postfix operator. At the same time, it must necessarily return either a link or a pointer that allows you to access the object.
An assignment operator is also defined only as a member of a class due to its association with the left operand.
Assignment operators "=", addresses "&" and sequences "," must be defined in the public block.
Total
Overloading operators helps implement one of the key aspects of OOP about polymorphism. But it’s important to understand that overloading is nothing more than a different way to call functions. The task of operator overloading often consists in improving the understanding of the code, rather than in ensuring a gain in some issues.
And that's not all. It should also be noted that operator overloading is a complex mechanism with many pitfalls. Therefore it is very easy to make a mistake. This is the main reason why most programmers advise to refrain from using operator overload and resort to it only as a last resort and with complete confidence in their actions.
Recommendations
- Perform operator overloading only to simulate a familiar recording. To make the code more readable. If the code becomes more complex in structure or readability, you should abandon operator overloading and use functions.
- For large operands, to save space, use arguments with the type of constant references to pass.
- Optimize return values.
- Do not touch the copy operation if it is suitable for your class.
- If copying is not suitable by default, change or explicitly prohibit copying.
- Member functions should be preferred over non-member functions in cases where the function requires access to the class representation.
- Specify a namespace and indicate the relationship of functions with their class.
- Use non-member functions for symmetric operators.
- Use the () operator for indices in multidimensional arrays.
- Use implicit conversions with caution.