Константні функції-члени - Ефективне використання C

const char& operator[](std::size_t position) const // operator[] для

char& operator[](std::size_t position) // operator[] для

Функцію operator[] у класі TextBlock можна використовувати так:

const TextBlock ctb(“World”);

До речі, константні об'єкти найчастіше зустрічаються у реальних програмах у результаті передачі за вказівником чи посилання константу. Наведений вище приклад CTB є досить штучним. Але ось вам більш реалістичний:

// на константний об'єкт

Перевантажуючи operator[] і створюючи різні версії з різними типами, що повертаються, ви можете по-різному обробляти константні і неконстантні об'єкти TextBlock:

tb[0] = 'x'; // нормально - пишеться

ctb[0] = 'x'; // Помилка! - Запис

Зазначимо, що помилка тут пов'язана лише з типом значення, що повертається operator[]; сам виклик operator[] відбувається нормально. Причина помилки – у спробі надати значення об'єкту типу const char&, тому що це саме такий тип повертається константною версією operator[].

Це пояснюється тим, що значення вбудованого типу, що повертається функцією, модифікувати некоректно. Навіть якби це було припустимо, той факт, що C++ повертає об'єкти за значенням (див. правило 20), означав би таке: модифікувалася копія tb.text[0], а чи не саме значення tb.text[0]. Навряд чи це те, на що ви очікуєте.

Давайте трохи перепочинемо і пофілософствуємо. Що означає для функції члена бути константною? Існує два поширених поняття: побітова константність (також відома як фізична константність) і логічна константність.

Прихильники побітової константності вважають, що функція-член константна тоді і тільки тоді, коли вона не модифікує жоднихдані-члени об'єкта (за винятком статичних), тобто не модифікує жодного біта всередині об'єкта. Визначення побітової константності добре тим, що її порушення легко виявити: компілятор просто шукає присвоєння членам класу. Фактично, побітова константність – це константність, визначена в C++: функція-член із модифікатором const не може модифікувати нестатичні дані-члени об'єкта, для якого вона викликана.

На жаль, багато функцій-членів, які поводяться далеко не константно, проходять побітовий тест. Зокрема, функція-член, яка модифікує те, на що вказує покажчик, часто не веде себе як константна. Але якщо об'єкту належить лише покажчик, то функція формально є побітово константною, і компілятор не заперечуватиме. Це може призвести до несподіваної поведінки. Наприклад, припустимо, що є клас подібний до Text-Block, де дані зберігаються в рядках типу char * замість string, оскільки це необхідно для передачі в функції, написані мовою C, який не розуміє, що таке об'єкти типу string.

char& operator[](std::size_t position) const // невдале (але побітово

У цьому класі функцію operator[] (неправильно!) оголошено як константну функцію-член, хоча вона повертає посилання на внутрішні дані об'єкта (ця тема обговорюється в правилі 28). Залишимо це поки що осторонь і відзначимо, що реалізація operator[] не модифікує pText. У результаті компілятор спокійно згенерує код функції operator[]. Адже вона справді є побітово константною, а це все, що компілятор може перевірити. Але подивіться, що відбувається:

char &pc = &cctb[0]; // виклик const operator[] для отримання

// покажчика на дані cctb

*pc = 'j'; // cctb тепер маєзначення “Jello”

Безперечно, є щось некоректне в тому, що ви створюєте константний об'єкт з певним значенням, викликаєте для нього лише константну функцію-член, проте змінюєте його значення!

Це призводить до поняття логічної константності. Прихильники цієї філософії стверджують, що функції-члени з const можуть модифікувати деякі біти об'єкта, що викликав їх, але тільки так, щоб користувач не міг цього виявити. Наприклад, ваш клас CTextBlock міг би кешувати довжину текстового блоку при кожному запиті:

std::size_t length() const;

std::size_t textLength; // останнє обчислене значення довжини

bool lengthIsValid; // Чи коректна довжина в даний момент

std::size_t CtextBlock::length() const

textLength = std::strlen(pText); // Помилка! Не можна надавати

> // LengthIsValid у константній

Ця реалізація length(), звичайно, не є побітово константною, оскільки може модифікувати значення членів textLength і lengthlsValid. Але в той же час з боку здається, що константність об'єктів CTextBlock не загрожує. Проте компілятор не згоден. Він наполягає на побітовій константності. Що робити?

Рішення просте: використовуйте модифікатор mutable. Він звільняє нестатичні дані-члени від обмежень побітової константності:

std::size_t length() const;

mutable std::size_t textLength; // Ці дані-члени завжди можуть бути

mutable bool lengthIsValid; // модифіковані, навіть у константних

std::size_t CtextBlock::length() const

textLength = std::strlen(pText); // тепер порядок