|
Программирование >> Оптимизация возвращаемого значения
приходится предполагать, что все вызовы ne-cons t operator [ ] выполняются для записи. (Вы можете различать запись и чтение при помощи proxy-классов, см. правило 30.) Для безопасной реализации не-const operator [ ] надо убедиться, что ни один из других объектов string, совместно использующих изменяемое значение StringValue, не будет изменен при выполнении предполагаемой записи. Короче говоря, вы должны быть уверены, что счетчик ссылок для соответствующего объекта StringValue равен единице всегда, когда вы возвращаете ссылку на символ внутри объекта StringValue. Вот как это сделать: char& String::operator[](int index) { Если значение совместно используется другими объектами String, создать отдельную копию значения, if (value->refCount > 1) { Уменьшить счетчик ссылок --value->refCount; refCount для текуш;его значения - оно больше не будет использоваться, value = Сделать копию значения, new StringValue(value->data); Вернуть ссылку на символ в созданном отдельном объекте StringValue. return value->data[index]; Эта идея - совместное использование значения до тех пор, пока не нужно будет записать что-либо в отдельную его копию, - имеет давнюю и славную историю в информатике, в особенности в операционных системах, где процессы могут совместно использовать страницы до тех пор, пока им не потребуется изменить данные в своей копии страницы. Описанный метод достаточно распространен и называется копирование при записи (copy-on-write). Это специфическая разновидность более общего подхода к увеличению эффективности - отложенного вычисления (см. правило 17). Указатели, ссылки и копирование при записи Эта реализация копирования при записи почти позволяет сохранить и правильность, и эффективность. Остается одна давняя проблема. Рассмотрим следующий код: string si = Hello ; char *p = &sl[1] ; Структура данных будет при этом выглядеть примерно так, как показано на рис. 5.13. Рис. 5.13 Теперь рассмотрим еще один оператор: string s2 = si; Конструктор копирования класса String позволяет объектам s2 и si совместно использовать значение StringValue, поэтому полученная в результате структура данных будет иметь вид, представленный на рис. 5.14. :©-
Рис. 5.14 Тогда происходит следующее: *р = X ; Изменяются и si, и s2 ! Конструктор копирования класса String не может обнаружить эту проблему, так как нельзя узнать, существует ли указатель на объект StringValue со значением объекта si. И проблема не ограничивается указателями: она будет возникать и в том случае, если кто-либо сохранил ссылку на результат вызова не-const operator [] класса String. С этой проблемой можно справиться тремя различными способами. Первый состоит в том, чтобы игнорировать ее, сделать вид, что она не существует. Такой подход, к несчастью, слишком часто встречается в библиотеках классов, реализующих строки с подсчетом ссылок. Если вы располагаете одной из подобных библиотек, проверьте ее, выполнив вышеприведенный пример. Если вы не уверены, выполняется ли в классе подсчет ссылок, все равно попробуйте выполнить пример. Благодаря чуду инкапсуляции может оказаться, что вы все же используете такой тип, даже не зная об этом. Но проблема игнорируется не во всех реализациях. Несколько более сложный способ справиться с трудностями - объявить их использование недопустимым. В документации таких реализаций обычно говорится примерно следующее: Не делайте этого, в противном случае результат будет неопределенным . Если вы все же сделаете это, сознательно или нет, и жалуетесь на результаты, вам отвечают: Мы же вас предупреждали . Такие реализации часто достаточно эффективны, но удобство их использования оставляет желать лучшего. * Тип string в стандартной библиотеке С++ (см. правило 35) использует комбинацию второго и третьего решений. Гарантируется, что ссьшка, возвращаемая не-const operator [ ], является корректной до следующего вызова функции, который может изменить данную строку. После этого использование ссьшки (или символа, на который она указывает) дает неопределенный результат. Это позволяет восстанавливать значение true для флага после каждого вызова функции, которая могла изменить строку Существует и третье решение, которое состоит в устранении проблемы. Реализовать его несложно, но оно иногда уменьшает степень совместного использования значений объектами. Суть решения в следующем: к каждому объекту StringValue добавляется флаг, показывающий, может ли объект использоваться совместно. Первоначально флаг устанавливается (объект может использоваться совместно), а затем сбрасывается при вызове для представленного объектом значения не-const operator [ ]. После того как этот флаг принимает значение false, оно остается таким навсегда.* Вот измененная версия StringValue, включающая флаг, которая определяет возможность совместного использования: class String { private: struct StringValue { int refCount; bool shareable; Добавить это. char *data; StringValue(const char *initValue); -StringValue0; string::StringValue::StringValue(const char *initValue) : refCount(1), shareable(true) И это. data = new char[strlen(initValue) + 1]; strcpY(data, initValue); String::StringValue::-StringValue() { delete [] data; Как видите, здесь нужны лишь небольшие изменения: две строки, которые их требуют, помечены комментариями. Соответственно, необходимо обновить и функции - члены класса String, чтобы учесть наличие поля shareable. Вот как это можно сделать для конструктора копирования: string::String(const String& rhs) if (rhs.value->shareable) {
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |