членами является вполне разумным, поэтому 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);
Конечно, вы можете создать подкласс одного из классов потока стандартной библиотеки и добавить все необходимые вам операторы сдвига влево, но будет ли такое решение действительно удачным? При таком решении только тот программный код, который использует ваш новый класс потока, сможет записывать в него объекты вашего специального класса. Если вы используете независимый оператор,

 
                