📜 ⬆️ ⬇️

Operator Overloading in C ++

Good day!

The desire to write this article appeared after reading the post Overloading C ++ operators , because many important topics were not covered in it.

The most important thing to remember is operator overloading, it’s just a more convenient way to call functions, so don’t get carried away with operator overloading. It should be used only when it is easier to write code. But not enough to make it difficult to read. After all, as you know, the code is read much more often than it is written. And do not forget that you will never be allowed to overload operators in a tandem with built-in types, the possibility of overloading is only for custom types / classes.

Overload syntax


The syntax for operator overloading is very similar to the definition of a function named operator @, where @ is the operator identifier (for example +, -, <<, >>). Consider the simplest example:
class Integer { private: int value; public: Integer(int i): value(i) {} const Integer operator+(const Integer& rv) const { return (value + rv.value); } }; 

In this case, the operator is designed as a member of the class, the argument specifies the value on the right side of the operator. In general, there are two main ways of overloading operators: global functions, class- friendly , or inline functions of the class itself. What way, for which operator is better, consider at the end of the topic.
')
In most cases, operators (except conditional) return an object, or a reference to the type to which its arguments relate (if the types are different, then you decide how to interpret the result of the calculation of the operator).

Overloading unary operators


Consider examples of overloading unary operators for the Integer class defined above. At the same time, we define them as friendly functions and consider decrement and increment operators:
 class Integer { private: int value; public: Integer(int i): value(i) {} // + friend const Integer& operator+(const Integer& i); // - friend const Integer operator-(const Integer& i); //  friend const Integer& operator++(Integer& i); //  friend const Integer operator++(Integer& i, int); //  friend const Integer& operator--(Integer& i); //  friend const Integer operator--(Integer& i, int); }; //    . const Integer& operator+(const Integer& i) { return i.value; } const Integer operator-(const Integer& i) { return Integer(-i.value); } //      const Integer& operator++(Integer& i) { i.value++; return i; } //      const Integer operator++(Integer& i, int) { Integer oldValue(i.value); i.value++; return oldValue; } //      const Integer& operator--(Integer& i) { i.value--; return i; } //      const Integer operator--(Integer& i, int) { Integer oldValue(i.value); i.value--; return oldValue; } 

Now you know how the compiler distinguishes between prefix and postfix versions of decrement and increment. In the case when he sees the expression ++ i, then the function operator ++ (a) is called. If he sees i ++, operator ++ (a, int) is called. That is, the overloaded function operator ++ is called, and it is for this that the dummy int parameter is used in the postfix version.

Binary operators


Consider the syntax for overloading binary operators. We will overload one operator that returns the l-value, one conditional operator, and one operator that creates a new value (we define them globally):
 class Integer { private: int value; public: Integer(int i): value(i) {} friend const Integer operator+(const Integer& left, const Integer& right); friend Integer& operator+=(Integer& left, const Integer& right); friend bool operator==(const Integer& left, const Integer& right); }; const Integer operator+(const Integer& left, const Integer& right) { return Integer(left.value + right.value); } Integer& operator+=(Integer& left, const Integer& right) { left.value += right.value; return left; } bool operator==(const Integer& left, const Integer& right) { return left.value == right.value; } 

In all these examples, operators are overloaded for one type, however, this is not necessary. It is possible, for example, to overload the addition of our type Integer and Float defined by its similarity.

Arguments and Return Values


As you can see, the examples use different ways to pass arguments to functions and return operator values.


Return value optimization


When creating new objects and returning them from a function, use the record as for the above-described example of the binary plus operator.
 return Integer(left.value + right.value); 

Honestly, I don’t know which situation is relevant for C ++ 11, all the reasoning is further valid for C ++ 98.
At first glance, this is similar to the syntax for creating a temporary object, that is, as if there is no difference between the code above and this:
 Integer temp(left.value + right.value); return temp; 

But in fact, in this case the constructor will be called in the first line, then the copy constructor will be called, which will copy the object, and then, when the stack is expanded, the destructor will be called. When using the first record, the compiler initially creates an object in memory into which to copy it, thus saving a call to the copy constructor and destructor.

Special operators


In C ++, there are statements that have a specific syntax and overload method. For example, the index operator []. It is always defined as a member of the class and, since it implies the behavior of the object being indexed as an array, it should return a reference.

Operator comma

The “special” operators also include the comma operator. It is called for objects that have a comma next to it (but it is not called in function argument lists). It’s not so easy to come up with a meaningful example of using this operator. Habrayuzer AxisPod in the comments to the previous article on overload told about one .

Pointer dereference operator

Overloading these operators can be justified for classes of smart pointers. This operator is necessarily defined as a function of a class, and some restrictions are imposed on it: it must return either an object (or reference) or a pointer that allows access to the object.

Assignment operator

The assignment operator is necessarily defined as a class function, because it is inextricably linked with the object to the left of the "=". The definition of the assignment operator in a global form would make it possible to redefine the standard behavior of the operator "=". Example:
 class Integer { private: int value; public: Integer(int i): value(i) {} Integer& operator=(const Integer& right) { //   if (this == &right) { return *this; } value = right.value; return *this; } }; 


As you can see, a self-assignment check is performed at the beginning of the function. In general, in this case, self-assignment is harmless, but the situation is not always so simple. For example, if the object is large, you can spend a lot of time on unnecessary copying, or when working with pointers.

Non-transferable operators

Some operators in C ++ are not overloaded in principle. Apparently, this is done for security reasons.



Recommendations to the form of definition of operators


As we have already found out, there are two ways of operators - in the form of a class function and in the form of a friendly global function.
Rob Murray, in his book C ++ Strategies and Tactics, defined the following guidelines for choosing the form of an operator:
Operator
Recommended form
All unary operators
Class member
= () [] -> -> *
Required class member
+ = - = / = * = ^ = & = | =% = >> = << =
Class member
Remaining binary operators
Not a member of the class


Why is that? First, some operators were initially restricted. In general, if semantically there is no difference in how to define an operator, then it is better to arrange it as a function of a class in order to emphasize the connection, plus in addition the function will be inline. In addition, sometimes it may be necessary to represent the left-hand operand as an object of another class. Probably the most striking example is redefining << and >> for input / output streams.

Literature


Bruce Ekkel - Philosophy C ++. Introduction to standard C ++ .

Source: https://habr.com/ru/post/132014/


All Articles