Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ | страница 26




>class TextBlock {

>public:

>...

>const char& operator[](std::size_t position) const

>{

>... // выполнить проверку границ массива

>... // протоколировать доступ к данным

>... // проверить целостность данных

>return text[position];

>}

>char& operator[](std::size_t position) const

>{

>... // выполнить проверку границ массива

>... // протоколировать доступ к данным

>... // проверить целостность данных

>return text[position];

>}

>private:

>std:string text;

>};


Ох! Налицо все неприятности, связанные с дублированием кода: увеличение времени компиляции, размера программы и неудобство сопровождения. Конечно, можно переместить весь код для проверки выхода за границы массива и прочего в отдельную функцию-член (естественно, закрытую), которую будут вызывать обе версии operator[], но обращения к этой функции все же будут дублироваться.

В действительности было бы желательно реализовать функциональность operator[] один раз, а использовать в двух местах. То есть одна версия operator[] должна вызывать другую. И это подводит нас к вопросу об отбрасывании константности.

С самого начала отметим, отбрасывать константность нехорошо. Я посвятил целое правило 27 тому, чтобы убедить вас не делать этого, но дублирование кода – тоже не сахар. В данном случае константная версия operator[] делает в точности то же самое, что неконстантная, и отличие между ними – лишь в присутствии модификатора const. В этой ситуации отбрасывать const безопасно, поскольку пользователь, вызывающий неконстантный operator[], так или иначе должен получить неконстантный объект. Ведь в противном случае он не стал бы вызывать неконстантную функцию. Поэтому реализация неконстантного operator[] путем вызова константной версии – это безопасный способ избежать дублирования кода, даже пусть даже для этого требуется воспользоваться оператором const_cast. Ниже приведен получающийся в результате код, но он станет яснее после того, как вы прочитаете следующие далее объяснения:


>class TextBlock {

>public:

>...

>const char& operator[](std::size_t position) const // то же, что и раньше

>{

>...

>...

>...

>return text[position];

>}

>char& operator[](std::size_t position) const // теперь просто

>// вызываем const op[]

>{

>return

>const_cast( // из возвращаемого типа

>// op[] исключить const

>static_cast(*this) // добавить const типу

>// *this

>[position] // вызвать константную

>); // версию op[]

>}

>...

>};


Как видите, код включает два приведения, а не одно. Мы хотим, чтобы неконстантный operator[] вызывал константный, но если внутри неконстантного оператора [] просто вызовем operator[], то получится рекурсивный вызов. Во избежание бесконечной рекурсии нужно указать, что мы хотим вызвать const operator[], но прямого способа сделать это не существует. Поэтому мы приводим *this от типа TextBlock& к const TextBlock&. Да, мы выполняем приведение, чтобы