членами является вполне разумным, поэтому C++ имеет механизм его поддержки: ключевое слово mutable
.
Для того чтобы curIndex_
можно было обновлять в константной функции-члене, объявите ее с ключевым словом mutable в объявлении класса.
mutable int curIndex_;
Это позволит вам модифицировать curIndex_
в любом месте. Однако этой возможностью следует пользоваться разумно, поскольку это действует на вашу функцию так, как будто она становится с этого момента неконстантной.
Применение ключевого слова const
в примере 15.4 позволяет гарантировать невозможность изменения состояния объекта в функции-члене. В целом, такой подход дает хорошие результаты, потому что сообщает пользователям класса о режиме работы функции-члена и потому что сохраняет вам репутацию, заставляя компилятор проконтролировать отсутствие в функции-члене непредусмотренных действий.
15.5. Написание оператора, не являющегося функцией-членом
Необходимо написать бинарный оператор, и вы не можете или не хотите сделать его функцией- членом класса.
Используйте ключевое слово operator
, временную переменную и конструктор копирования для выполнения основной работы и возвратите временный объект. В примере 15.5 приводится простой оператор конкатенации строк для пользовательского класса String
.
#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() << '
';
std::cout << 's4 = ' << s4.data() << '
';
std::cout << 's5 = ' << s5.data() << '
';
std::cout << 's6 = ' << s6.data() << '
';
}
Независимый оператор объявляется и определяется подобно оператору функции-члена. В примере 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);
Конечно, вы можете создать подкласс одного из классов потока стандартной библиотеки и добавить все необходимые вам операторы сдвига влево, но будет ли такое решение действительно удачным? При таком решении только тот программный код, который использует ваш новый класс потока, сможет записывать в него объекты вашего специального класса. Если вы используете независимый оператор,