Operator overloading in C++. Methods of application

In we looked at the basic aspects of using operator overloading. In this material, overloaded C++ operators will be presented to your attention. Each section is characterized by semantics, i.e. expected behavior. In addition, typical ways to declare and implement operators will be shown.

In the code examples, X indicates the user-defined type for which the operator is implemented. T is an optional type, either user-defined or built-in. The parameters of the binary operator will be named lhs and rhs . If an operator is declared as a class method, its declaration will be prefixed with X:: .

operator=

  • Definition from right to left: Unlike most operators, operator= is right associative, i.e. a = b = c means a = (b = c) .

Copy

  • Semantics: assignment a = b . The value or state of b is passed to a . Additionally, a reference to a is returned. This allows you to create chains like c = a = b.
  • Typical ad: X& X::operator= (X const& rhs) . Other types of arguments are possible, but these are not used often.
  • Typical implementation: X& X::operator= (X const& rhs) ( if (this != &rhs) ( //perform element wise copy, or: X tmp(rhs); //copy constructor swap(tmp); ) return *this; )

Move (since C++11)

  • Semantics: assignment a = temporary() . The value or state of the right value is assigned to a by moving the contents. A reference to a is returned.
  • : X& X::operator= (X&& rhs) ( //take the guts from rhs return *this; )
  • Compiler generated operator= : The compiler can only create two kinds of this operator. If the operator is not declared in the class, the compiler tries to create public copy and move operators. Since C++11, the compiler can create a default operator: X& X::operator= (X const& rhs) = default;

    The generated statement simply copies/moves the specified element if such an operation is allowed.

operator+, -, *, /, %

  • Semantics: operations of addition, subtraction, multiplication, division, division with a remainder. A new object with the resulting value is returned.
  • Typical declaration and implementation: X operator+ (X const lhs, X const rhs) ( X tmp(lhs); tmp += rhs; return tmp; )

    Typically, if operator+ exists, it makes sense to also overload operator+= in order to use the notation a += b instead of a = a + b . If operator+= is not overloaded, the implementation will look something like this:

    X operator+ (X const& lhs, X const& rhs) ( // create a new object that represents the sum of lhs and rhs: return lhs.plus(rhs); )

Unary operator+, –

  • Semantics: positive or negative sign. operator+ usually doesn't do anything and is therefore hardly used. operator- returns the argument with the opposite sign.
  • Typical declaration and implementation: X X::operator- () const ( return /* a negative copy of *this */; ) X X::operator+ () const ( return *this; )

operator<<, >>

  • Semantics: In built-in types, operators are used to bit shift the left argument. Overloading these operators with exactly this semantics is rare; the only one that comes to mind is std::bitset . However, new semantics have been introduced for working with streams, and I/O operator overloading is quite common.
  • Typical declaration and implementation: since you cannot add methods to standard iostream classes, shift operators for classes you define must be overloaded as free functions: ostream& operator<< (ostream& os, X const& x) { os << /* the formatted data of rhs you want to print */; return os; } istream& operator>> (istream& is, X& x) ( SomeData sd; SomeMoreData smd; if (is >> sd >> smd) ( rhs.setSomeData(sd); rhs.setSomeMoreData(smd); ) return lhs; )

    In addition, the type of the left operand can be any class that should behave like an I/O object, that is, the right operand can be a built-in type.

    MyIO& MyIO::operator<< (int rhs) { doYourThingWith(rhs); return *this; }

Binary operator&, |, ^

  • Semantics: Bit operations “and”, “or”, “exclusive or”. These operators are overloaded very rarely. Again, the only example is std::bitset .

operator+=, -=, *=, /=, %=

  • Semantics: a += b usually means the same as a = a + b . The behavior of other operators is similar.
  • Typical definition and implementation: Since the operation modifies the left operand, hidden type casting is not desirable. Therefore, these operators must be overloaded as class methods. X& X::operator+= (X const& rhs) ( //apply changes to *this return *this; )

operator&=, |=, ^=,<<=, >>=

  • Semantics: similar to operator+= , but for logical operations. These operators are overloaded just as rarely as operator| etc. operator<<= и operator>>= are not used for I/O operations because operator<< и operator>> already changing the left argument.

operator==, !=

  • Semantics: Test for equality/inequality. The meaning of equality varies greatly by class. In any case, consider the following properties of equalities:
    1. Reflexivity, i.e. a == a .
    2. Symmetry, i.e. if a == b , then b == a .
    3. Transitivity, i.e. if a == b and b == c , then a == c .
  • Typical declaration and implementation: bool operator== (X const& lhs, X cosnt& rhs) ( return /* check for whatever means equality */ ) bool operator!= (X const& lhs, X const& rhs) ( return !(lhs == rhs); )

    The second implementation of operator!= avoids repetition of code and eliminates any possible ambiguity regarding any two objects.

operator<, <=, >, >=

  • Semantics: check for ratio (more, less, etc.). It is usually used if the order of elements is uniquely defined, that is, it makes no sense to compare complex objects with several characteristics.
  • Typical declaration and implementation: bool operator< (X const& lhs, X const& rhs) { return /* compare whatever defines the order */ } bool operator>(X const& lhs, X const& rhs) ( return rhs< lhs; }

    Implementing operator> using operator< или наоборот обеспечивает однозначное определение. operator<= может быть реализован по-разному, в зависимости от ситуации . В частности, при отношении строго порядка operator== можно реализовать лишь через operator< :

    Bool operator== (X const& lhs, X const& rhs) ( return !(lhs< rhs) && !(rhs < lhs); }

operator++, –

  • Semantics: a++ (post-increment) increments the value by 1 and returns old meaning. ++a (preincrement) returns new meaning. With decrement operator-- everything is similar.
  • Typical declaration and implementation: X& X::operator++() ( //preincrement /* somehow increment, e.g. *this += 1*/; return *this; ) X X::operator++(int) ( //postincrement X oldValue(*this); + +(*this); return oldValue;

operator()

  • Semantics: execution of a function object (functor). Typically used not to modify an object, but to use it as a function.
  • No restrictions on parameters: Unlike previous operators, in this case there are no restrictions on the number and type of parameters. An operator can only be overloaded as a class method.
  • Example ad: Foo X::operator() (Bar br, Baz const& bz);

operator

  • Semantics: access elements of an array or container, for example in std::vector , std::map , std::array .
  • Announcement: The parameter type can be anything. The return type is usually a reference to what is stored in the container. Often the operator is overloaded in two versions, const and non-const: Element_t& X::operator(Index_t const& index); const Element_t& X::operator(Index_t const& index) const;

operator!

  • Semantics: negation in a logical sense.
  • Typical declaration and implementation: bool X::operator!() const ( return !/*some evaluation of *this*/; )

explicit operator bool

  • Semantics: Use in a logical context. Most often used with smart pointers.
  • Implementation: explicit X::operator bool() const ( return /* if this is true or false */; )

operator&&, ||

  • Semantics: logical “and”, “or”. These operators are defined only for the built-in boolean type and operate on a lazy basis, that is, the second argument is considered only if the first does not determine the result. When overloaded, this property is lost, so these operators are rarely overloaded.

Unary operator*

  • Semantics: Pointer dereference. Typically overloaded for classes with smart pointers and iterators. Returns a reference to where the object points.
  • Typical declaration and implementation: T& X::operator*() const ( return *_ptr; )

operator->

  • Semantics: Access a field by pointer. Like the previous one, this operator is overloaded for use with smart pointers and iterators. If the -> operator is encountered in your code, the compiler redirects calls to operator-> if the result of a custom type is returned.
  • Usual implementation: T* X::operator->() const ( return _ptr; )

operator->*

  • Semantics: access to a pointer-to-field by pointer. The operator takes a pointer to a field and applies it to whatever *this points to, so objPtr->*memPtr is the same as (*objPtr).*memPtr . Very rarely used.
  • Possible implementation: template T& X::operator->*(T V::* memptr) ( return (operator*()).*memptr; )

    Here X is the smart pointer, V is the type pointed to by X , and T is the type pointed to by the field-pointer. Not surprisingly, this operator is rarely overloaded.

Unary operator&

  • Semantics: address operator. This operator is overloaded very rarely.

operator

  • Semantics: The built-in comma operator applied to two expressions evaluates them both in written order and returns the value of the second one. It is not recommended to overload it.

operator~

  • Semantics: Bitwise inversion operator. One of the most rarely used operators.

Casting operators

  • Semantics: Allows implicit or explicit casting of class objects to other types.
  • Announcement: //conversion to T, explicit or implicit X::operator T() const; //explicit conversion to U const& explicit X::operator U const&() const; //conversion to V& V& X::operator V&();

    These declarations look strange because they lack a return type. It is part of the operator name and is not specified twice. It is worth remembering that a large number of hidden casts can lead to unexpected errors in the program.

operator new, new, delete, delete

These operators are completely different from all the above ones because they do not work on user-defined types. Their overload is very complex and therefore will not be considered here.

Conclusion

The main idea is this: don't overload operators just because you know how to do it. Overload them only in cases where it seems natural and necessary. But remember that if you overload one operator, you will have to overload others.

Good day!

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

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

Overload Syntax

The syntax for operator overloading is very similar to defining a function called operator@, where @ is the operator identifier (for example +, -,<<, >>). Let's consider 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 framed as a member of a class, the argument determines the value located on the right side of the operator. In general, there are two main ways to overload operators: global functions that are friendly to the class, or inline functions of the class itself. We will consider which method is better for which operator at the end of the topic.

In most cases, operators (except conditional ones) return an object, or a reference to the type to which its arguments belong (if the types are different, then you decide how to interpret the result of the operator evaluation).

Overloading unary operators

Let's look at examples of unary operator overloading for the Integer class defined above. At the same time, let’s define them in the form of friendly functions and consider the decrement and increment operators:
class Integer ( private: int value; public: Integer(int i): value(i) () //unary + friend const Integer& operator+(const Integer& i); //unary - friend const Integer operator-(const Integer& i) ; //prefix increment friend const Integer& operator++(Integer& i); //postfix increment friend const Integer operator++(Integer& i, int); //prefix decrement friend const Integer& operator--(Integer& i); Integer operator--(Integer& i, int); //unary plus does nothing. const Integer& operator+(const Integer& i) ( return i.value; ) const Integer operator-(const Integer& i) ( return Integer(-i.value); ) //prefix version returns value after increment const Integer& operator++(Integer& i) ( i.value++; return i; ) //postfix version returns the value before the increment const Integer operator++(Integer& i, int) ( Integer oldValue(i.value); i.value++; return oldValue; ) //prefix version returns the value after decrement const Integer& operator--(Integer& i) ( i.value--; return i; ) //postfix version returns the value before decrement 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 it sees the expression ++i, the function operator++(a) is called. If it sees i++, then operator++(a, int) is called. That is, the overloaded operator++ function is called, and this is what the dummy int parameter in the postfix version is used for.

Binary operators

Let's look at the syntax for overloading binary operators. Let's overload one operator that returns an l-value, one conditional operator and one operator creating a new value (let's 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 of these examples, operators are overloaded for the same type, however, this is not necessary. You can, for example, overload the addition of our Integer type and Float defined by its similarity.

Arguments and return values

As you can see, the examples use different ways of passing arguments to functions and returning operator values.
  • If the argument is not modified by an operator, in the case of, for example, a unary plus, it must be passed as a reference to a constant. In general, this is true for almost all arithmetic operators (addition, subtraction, multiplication...)
  • The type of return value depends on the nature of the operator. If the operator must return a new value, then a new object must be created (as in the case of binary plus). If you want to prevent an object from being modified as an l-value, then you need to return it as a constant.
  • Assignment operators must return a reference to the changed element. Also, if you want to use the assignment operator in constructs like (x=y).f(), where the f() function is called for the variable x after assigning it to y, then do not return a reference to the constant, just return a reference.
  • Logical operators should return an int at worst, and a bool at best.

Return Value Optimization

When creating new objects and returning them from a function, you should use notation similar to the example of the binary plus operator described above.
return Integer(left.value + right.value);
To be honest, I don’t know what situation is relevant for C++11; all further arguments are valid for C++98.
At first glance, this looks similar to the syntax for creating a temporary object, that is, there seems to be 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 unwinding the stack, the destructor will be called. When using the first entry, the compiler initially creates the object in the memory into which it needs to be copied, thus saving calls to the copy constructor and destructor.

Special Operators

C++ has operators that have specific syntax and overloading methods. For example, the indexing operator. It is always defined as a member of a class and, since the indexed object is intended to behave like an array, it should return a reference.
Comma operator
The "special" operators also include the comma operator. It is called on objects that have a comma next to them (but it is not called on function argument lists). Coming up with a meaningful use case for this operator is not easy. Habrauser in the comments to the previous article about overload.
Pointer dereference operator
Overloading these operators can be justified for smart pointer classes. This operator is necessarily defined as a class function, and some restrictions are imposed on it: it must return either an object (or a 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 intrinsically linked to the object to the left of the "=". Defining the assignment operator globally would make it possible to override the default behavior of the "=" operator. Example:
class Integer ( private: int value; public: Integer(int i): value(i) () Integer& operator=(const Integer& right) ( //check for self-assignment if (this == &right) ( return *this; ) value = right.value; return *this;

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

Non-overloadable operators
Some operators in C++ are not overloaded at all. Apparently this was done for security reasons.
  • Class member selection operator ".".
  • Operator for dereferencing a pointer to a class member ".*"
  • C++ does not have the exponentiation operator (as in Fortran) "**".
  • It is forbidden to define your own operators (there may be problems with determining priorities).
  • Operator priorities cannot be changed
As we have already found out, there are two ways of operators - as a class function and as a friendly global function.
Rob Murray, in his book C++ Strategies and Tactics, defines the following guidelines for choosing operator form:

Why is this so? Firstly, some operators are initially limited. In general, if there is no semantic difference in how an operator is defined, then it is better to design it as a class function to emphasize the connection, plus in addition the function will be inline. In addition, sometimes there may be a need to represent the left-hand operand as an object of another class. Probably the most striking example is redefinition<< и >> for I/O streams.

Many programming languages ​​use operators: at a minimum, assignments (= , := or similar) and arithmetic operators(+, -, * and /). In most statically typed languages, these operators are bound to types. For example, in Java, addition with the + operator is only possible for integers, floating point numbers, and strings. If we define our own classes for mathematical objects, such as matrices, we can implement a method for adding them, but we can only call it with something like this: a = b.add(c) .

In C++ there is no such limitation - we can overload almost any known operator. The possibilities are endless: you can choose any combination of operand types, the only limitation being that at least one user-defined type operand must be present. That is, define a new operator over built-in types or rewrite an existing one it is forbidden.

When should you overload operators?

Remember the main thing: overload operators if and only if it makes sense. That is, if the meaning of the overload is obvious and does not contain hidden surprises. Overloaded operators must act in the same way as their basic versions. Naturally, exceptions are allowed, but only in cases where they are accompanied by clear explanations. A good example is the operators<< и >> the iostream standard library, which clearly do not behave like normal operators.

Here are good and bad examples of operator overloading. The above matrix addition is an illustrative case. Here, overloading the addition operator is intuitive and, if implemented correctly, does not require explanation:

Matrix a, b; Matrix c = a + b;

An example of a bad addition operator overload would be the addition of two “player” objects in a game. What did the creator of the class mean? What will be the result? We don't know what the operation does, and therefore using this operator is dangerous.

How to overload operators?

Operator overloading is similar to overloading functions with special names. In fact, when the compiler sees an expression that contains an operator and a user-defined type, it replaces the expression with a call to the corresponding function of the overloaded operator. Most of their names begin with keyword operator followed by the corresponding operator designator. When the designation does not consist of special characters, for example, in the case of a type conversion or memory management operator (new, delete, etc.), the word operator and the operator designation must be separated by a space (operator new), otherwise the space can be ignored (operator+ ).

Most operators can be overloaded either by class methods or by simple functions, but there are a few exceptions. When the overloaded operator is a method of a class, the type of the first operand must be that class (always *this), and the second must be declared in the parameter list. Additionally, method statements are not static, with the exception of memory management statements.

When you overload an operator in a class method, it gains access to the private fields of the class, but the hidden conversion of the first argument is not available. Therefore, binary functions are usually overloaded as free functions. Example:

Class Rational ( public: //Constructor can be used for implicit conversion from int: Rational(int numerator, int denominator = 1); Rational operator+(Rational const& rhs) const; ); int main() ( Rational a, b, c; int i; a = b + c; //ok, no conversion necessary a = b + i; //ok, implicit conversion of the second argument a = i + c; //ERROR: first argument can not be implicitly converted)

When unary operators are overloaded as free functions, hidden argument conversion is available to them, but this is not usually used. On the other hand, this property is necessary for binary operators. Therefore, the main advice would be the following:

Implement unary operators and binary operators like “ X=” in the form of class methods, and other binary operators - in the form of free functions.

Which operators can be overloaded?

We can overload almost any C++ operator, subject to the following exceptions and limitations:

  • You cannot define a new operator, such as operator** .
  • The following operators cannot be overloaded:
    1. ?: (ternary operator);
    2. :: (access to nested names);
    3. . (access to fields);
    4. .* (access to fields by pointer);
    5. sizeof , typeid and cast operators.
  • The following operators can only be overloaded as methods:
    1. = (assignment);
    2. -> (access to fields by pointer);
    3. () (function call);
    4. (access by index);
    5. ->* (access to pointer-to-field by pointer);
    6. conversion and memory management operators.
  • The number of operands, execution order, and associativity of operators is determined by the standard version.
  • At least one operand must be a user type. Typedef doesn't count.

The next part will introduce C++ operator overloads, in groups and individually. Each section is characterized by semantics, i.e. expected behavior. In addition, typical ways to declare and implement operators will be shown.

The minimal assignment operator is

Void Cls::operator=(Cls other) ( swap(*this, other); )

According to the standard, this is copying assignment operator.
However, it can also perform a move if Cls has a move constructor:

Cls a, b; a = std::move(b); // Works like // Cls other(std::move(b)); a.operator=(other); // ^^^^^^^^^^ // move: call Cls::Cls(Cls&&)

After a swap, the current class members end up in a temporary other object and are deleted when the assignment statement exits.
A copy assignment to itself will make an extra copy, but there will be no errors.

The result type can be anything.
The automatically generated assignment operator has a return type of Cls& and returns *this . This allows you to write code like a = b = c or (a = b) > c .
But many code style conventions do not approve of this, in particular see CppCoreGuidelines ES.expr "Avoid complicated expressions" .

To make this assignment operator work, you need copy/move constructors and a swap function.
Together it looks like this:

Class Cls ( public: Cls() () // Copy constructor Cls(const Cls& other) : x(other.x), y(other.y) () // Move constructor Cls(Cls&& other) noexcept ( swap(* this, other); ) // Assignment operator void operator=(Cls other) noexcept ( swap(*this, other); ) // Exchange friend void swap(Cls& a, Cls& b) noexcept ( using std::swap; / / Adding a standard function to the list of overloads... swap(a.x, b.x); // ... and calling using argument type search (ADL) private: X Y y; );

The copy constructor copies each member of the class.

The move constructor constructs empty class members, and then exchanges them with its argument. You can move each member individually, but it is more convenient to use swap .

The swap function can be a free friend function. Many algorithms expect a free swap function, and call it via argument type lookup (ADL).
Previously it was also recommended to write method swap so that you can write f().swap(x); , but with the advent of move semantics this was no longer necessary.

If functions cannot throw exceptions, then they should be marked as noexcept. This is needed for std::move_if_noexcept and other functions that can use more efficient code if the assignment or constructor does not throw exceptions. For this class issue

Std::is_nothrow_copy_constructible == 0 std::is_nothrow_move_constructible == 1 std::is_nothrow_copy_assignable == 0 std::is_nothrow_move_assignable == 1

Although the assignment operator is marked noexcept , calling it with a const Cls& argument will result in copying, which may throw an exception. Therefore is_nothrow_copy_assignable returns false .

Sometimes you want to be creative and make it easier program code for yourself and for others. Writing for yourself, understanding for others. Let's say, if in our program we often encounter the function of adding one line to the end of another, of course, we can implement this in different ways. And if we, in some part of our code, write, for example, like this:

Char str1 = "Hello"; char str2 = "world!"; str1 + str2;

and as a result we get the string “Hello world!” Wouldn't that be great? Well, please! Today you will learn to “explain” to the computer that with the + operator you want to add not two numbers, but two strings. And working with strings is one of the most successful examples, in my opinion, to start understanding the topic of “Operator Overloading”.

Let's start practicing. In this example, we will overload the + operator and force it to append the contents of another line to one line. Namely: we will collect from four separate lines a part of a poem by A.S. Pushkin known to all of us. I advise you to open your development environment and rewrite this example. If you don't understand everything in the code, don't worry, detailed explanations will be given below.

#include #include using namespace std; class StringsWork ( private: char str;//string that is available to the class public: StringsWork()//constructor in which we clear the class string from garbage ( for(int i = 0; i< 256; i++) str[i] = "\0"; } void operator +(char*);//прототип метода класса в котором мы перегрузим оператор + void getStr();//метод вывода данных на экран }; void StringsWork::operator +(char *s) //что должен выполнить оператор + { strcat(str, s); //сложение строк } void StringsWork::getStr() { cout << str << endl << endl;//вывод символьного массива класса на экран } int main() { setlocale(LC_ALL, "rus"); char *str1 = new char ; //выделим память для строк char *str2 = new char ; char *str3 = new char ; char *str4 = new char ; strcpy(str1,"У лукоморья дуб зелёный;\n");//инициализируем strcpy(str2,"Всё ходит по цепи кругом;\n"); strcpy(str3,"И днём и ночью кот учёный\n"); strcpy(str4,"Златая цепь на дубе том:\n"); cout << "1) " << str1; cout << "2) " << str2; cout << "3) " << str3; cout << "4) " << str4 << endl; StringsWork story;//создаем объект и добавяем в него строки с помощью перегруженного + story + str1; story + str4; story + str3; story + str2; cout << "========================================" << endl; cout << "Стих, после правильного сложения строк: " << endl; cout << "========================================" << endl << endl; story.getStr(); //Отмечу, что для числовых типов данных оператор плюс будет складывать значения, как и должен int a = 5; int b = 5; int c = 0; c = a + b; cout << "========================================" << endl << endl; cout << "a = " << a << endl << "b = " << b << endl; cout << "c = " << a << " + " << b << " = " << c << endl << endl; delete str4;//освободим память delete str3; delete str2; delete str1; return 0; }

Let's figure it out:

We saw something new in the code in line 16 void operator +(char*); Here we declared a prototype of a class method in which we will overload our + operator. To overload an operator, you must use the reserved word operator . It looks like you are defining a regular function: void operator+ () (//code) In the body of this function we place code that will tell the compiler what the + operator (or some other operator) will do. An overloaded operator will perform the actions specified for it only within the scope of the class in which it is defined. Below, in lines 20 - 23 We are already determining what role + will play in our class. Namely, using the function strcat(str, s); it will append the contents of the string s, which we passed by pointer, to the end of the string str. Lines 17, 25 - 28 This is a regular class method that will display the class string on the screen. If you are not clear on how to define class methods outside the class body, i.e. such a moment as void StringsWork::getStr() (//definition), then you should first go here. Further, already in the main function main() , in lines 34 - 37, we create four pointers to strings and allocate the required amount of memory for each of them, not forgetting that for the character "\0" we also need to reserve one cell char *str1 = new char ; . Then we copy the text into them using the strcpy() function and display them on the screen - lines 39 - 47. And in line 49 create a class object. When it is created, the class constructor will work and the class line will be cleared of unnecessary data. Now all we have to do is add the strings in the correct sequence using the overloaded operator + - lines 50 - 53 and see what happened - line 58.

Result of the program:

1) There is a green oak near Lukomorye;
2) Everything goes round and round in a chain;
3) Day and night the cat is a scientist
4) Golden chain on the oak tree:

========================================
Verse, after correctly adding the lines:

There is a green oak near the Lukomorye;
Golden chain on the oak tree:
Day and night the cat is a scientist
Everything goes round and round in a chain;
========================================

a = 5
b = 5
c = 5 + 5 = 10

Operator Overloading Limits

  • Almost any operator can be overloaded, with the exception of the following:

. dot (select class element);

* asterisk (definition or dereference of a pointer);

:: double colon (method scope);

?: question mark with colon (ternary comparison operator);

# sharp (preprocessor symbol);

## double sharp (preprocessor symbol);

sizeof operator for finding the size of an object in bytes;

  • using overloading it is impossible to create new symbols for operations;
  • operator overloading does not change the order of operations and their priority;
  • unary operator cannot be used for override a binary operator in the same way that a binary operator will not override a unary operator.

Don't forget that in programming it is very desirable to do everything possible to make your code as clear as possible. This principle applies to everything: the names you give to variables, functions, structures, classes, as well as the actions that an overloaded operator will perform. Try to define these actions as close to the logical meaning of the operators as possible. For example, + to add strings or other class objects, - to delete a string, etc.

It should be noted that many programmers have a negative attitude towards operator overloading. The very possibility of operator overloading is provided to facilitate understanding and readability of program code. At the same time, on the contrary, it can become a reason for complicating your program and it will be difficult for many programmers to understand it. Remember the “golden mean” and only use overload when it will actually benefit you and others. It is quite possible to do without operator overloading. But this does not mean that you can ignore this topic. You should understand it, if only because you will someday have to deal with overloading in someone else's code and you can easily figure out what's what.

Here we have very briefly familiarized ourselves with operator overloading in C++. We saw, so to speak, the tip of the iceberg. And your homework (YES - HOMEWORK!) will be to modify the program by adding operator overloading to delete a line. Choose which operator to overload yourself. Or offer your own version of upgrading the code, adding to it what you consider necessary and interesting. You can add your “works” in the comments to this article. We will be interested to see your solutions. Good luck!


Close