C++. Сборник рецептов.

15.5. Написание оператора, не являющегося функцией-членом.

Проблема.

Необходимо написать бинарный оператор, и вы не можете или не хотите сделать его функцией-членом класса.

Решение.

Используйте ключевое слово operator, временную переменную и конструктор копирования для выполнения основной работы и возвратите временный объект. В примере 15.5 приводится простой оператор конкатенации строк для пользовательского класса String.

Пример 15.5. Конкатенация с использованием оператора не члена.

#include <iostream>

#include <cstring>

Class String { // Предположим, что объявление класса String содержит,

               // по крайней мере, все, что указанно ниже.

Public:

 String();

 String(const char* p);

 String(const String& orig);

 ~String() {delete buf_;}.

 String& append(const String& s);

 size_t length() const;

 const char* data() const;

 String& operator=(const String& orig);

 // ...

};

String operator+(const String& lhs, const String& rhs) {

 String tmp(lhs); // Сконструировать временный объект с помощью.

                  // конструктора копирования.

 tmp.append(rhs); // Использовать функцию-член для выполнения реальной.

                  // работы.

 return(tmp); // Возвратить временный объект.

}

Int main() {

 String s1("banana ");

 String s2("rancher");

 String s3, s4, s5, s6;

 s3 = s1 + s2;           // Работает хорошо, но с сюрпризами.

 s4 = s1 + "rama";       // Автоматически конструируется "rama", используя.

                         // конструктор String(const char*).

 s5 = "ham " + s2;       // Круто, то же самое можно делать даже.

 s6 = s1 + "rama " + s2; // с другим операндом.

 std::cout << "s3 = " << s3.data() << '\n';

 std::cout << "s4 = " << s4.data() << '\n';

 std::cout << "s5 = " << s5.data() << '\n';

 std::cout << "s6 = " << s6.data() << '\n';

}

Обсуждение.

Независимый оператор объявляется и определяется подобно оператору функции-члена. В примере 15.5 я мог бы реализовать operator+ как функцию-член, объявляя ее следующим образом.

String operator+(const String& rhs);

В большинстве случаев это будет работать одинаково, независимо от того, определяется ли operator+ как функция-член или нет, однако существует, по крайней мере, две причины, по которым желательно реализовать его не как функцию-член. Первая причина концептуальная, имеет ли смысл иметь оператор, который возвращает новый, отличный от других объект? operator+, реализованный как функция-член, не проверяет и не изменяет состояние объекта. Это служебная функция общего назначения, которая в данном случае работает со строками типа String и, следовательно, не должна являться функцией членом.

Вторая причина техническая. При использовании оператора-члена вы не сможете выполнить следующую операцию (из приведенного выше примера).

S5 = "ham " + s2;

Это не сработает, потому что символьная строка не имеет operator+, который принимает String в качестве параметра. С другой стороны, если вы определили независимый operator+, который принимает два параметра типа String, ваш компилятор проверит наличие в классе String конструктора, принимающего const char* в качестве аргумента (или любой другой тип, который вы используете совместно с String), и сконструирует временный объект на этапе выполнения. Поэтому приведенная выше строка эквивалентна следующей.

S5 = String("ham ") + s2;

Компилятор позволяет вам немного сэкономить ваши действия и не вводить несколько символов за счет поиска и вызова соответствующего конструктора.

Перегрузка операторов сдвига потоков влево и вправо ( << и >>) также требует применения операторов не-членов. Например, для записи нового объекта в поток, используя сдвиг влево, вам придется следующим образом объявить operator<<:

Ostream& operator<<(ostream& str, const MyClass& obj);

Конечно, вы можете создать подкласс одного из классов потока стандартной библиотеки и добавить все необходимые вам операторы сдвига влево, но будет ли такое решение действительно удачным? При таком решении только тот программный код, который использует ваш новый класс потока, сможет записывать в него объекты вашего специального класса. Если вы используете независимый оператор, любой программный код в том же самом пространстве имен сможет без проблем записать ваш объект в ostream (или считать его из istream).