42. Не допускайте вмешательства во внутренние дела
42. Не допускайте вмешательства во внутренние дела
Резюме
Избегайте возврата дескрипторов внутренних данных, управляемых вашим классом, чтобы клиенты не могли неконтролируемо изменять состояние вашего объекта, как своего собственного.
Обсуждение
Рассмотрим следующий код:
class Socket {
public:
// ... Конструктор, который открывает handle_,
// деструктор, который закрывает handle_, и т.д. ...
int GetHandle() const { return handle_; } // Плохо!
private:
int handle_; // дескриптор операционной системы
};
Сокрытие данных — мощный инструмент абстракции и модульности (см. рекомендации 11 и 41). Однако сокрытие данных при одновременном обеспечении доступа к их дескрипторам обречено на провал, потому что это то же, что и закрыть свою квартиру на замок и положить ключ под коврик у входа или просто оставить его в замке. Вот почему это так.
• В этом случае клиент имеет две возможности реализации функциональности. Он может воспользоваться абстракцией вашего класса (Socket) либо непосредственно работать с реализацией, на которой основан ваш класс (дескриптор сокета в стиле С). В последнем случае объект оказывается не осведомлен об изменениях, происходящих с ресурсом, которым он, как ему кажется, владеет. Теперь класс не в состоянии надежно обогатить или усовершенствовать функциональность (например, обеспечить прокси, журнализацию, сбор статистики и т.п.), поскольку клиенты могут просто обойти эти возможности реализации, как и любые другие инварианты, которые вы, как вы полагаете, добавили в ваш класс. Это делает невозможной, в частности, корректную обработку возникающих ошибок (см. рекомендацию 70).
• Класс не может изменять внутреннюю реализацию своей абстракции, поскольку от нее зависят клиенты. Если в будущем класс Socket будет обновлен для поддержки другого протокола с использованием других низкоуровневых примитивов, вызывающий код, который будет по-прежнему получать доступ к дескриптору handle_ и работать с ним, окажется некорректным.
• Класс не в состоянии обеспечить выполнение его инвариантов, поскольку вызывающий код может изменить состояние без ведома класса. Например, кто-то может закрыть дескриптор, используемый объектом Socket, минуя вызов функции-члена Socket, а это приведет к тому, что объект станет недействительным.
• Код клиента может хранить дескрипторы, возвращаемые вашим классом, и пытаться использовать их после того, как код вашего класса сделает их недействительными.
Распространенная ошибка заключается в том, что действие const на самом деле неглубокое и не распространяется посредством указателей (см. рекомендацию 15). Например, Socket::GetHandle — константный член; пока мы рассматриваем ситуацию с точки зрения компилятора, возврат handlе_ сохраняет константность объекта. Однако непосредственный вызов функций операционной системы с использованием значения handlе_ вполне может изменять данные, к которым косвенно обращается handlе_.
Приведенный далее пример очень прост, хотя в данном случае ситуация несколько лучше — мы можем снизить вероятность случайного неверного употребления возвращаемого значения, описав его тип как const:
class String {
char* buffer_;
public:
char* GetBuffer() const { return buffer_; }
// Плохо: следует возвращать const char*
// ...
};
Хотя функция GetBuffer константная, технически этот код вполне корректен. Понятно, что клиент может использовать эту функцию GetBuffer для того, чтобы изменить объект String множеством разных способов, не прибегая к явному преобразованию типов. Например, strcpy(s.GetBuffer(), "Very Long String...") — вполне законный код; любой компилятор пропустит его без каких бы то ни было замечаний. Если бы мы объявили возвращаемый тип как const char*, то представленный код вызвал бы, по крайней мере, ошибку времени компиляции, так что случайно поступить столь опасно было бы просто невозможно — вызывающий код должен был бы использовать явное преобразование типов (см. рекомендации 92 и 95).
Но даже возврат указателей на const не устраняет возможности случайного некорректного использования, поскольку имеется еще одна проблема, связанная с корректностью внутренних данных класса. В приведенном выше примере с классом String, вызывающий код может сохранить значение, возвращаемое функцией GetBuffer, а затем выполнить операции, которые приведут к росту (и перемещению) буфера String, что в результате может привести к использованию сохраненного, но более недействительного указателя на несуществующий в данный момент буфер. Таким образом, если вы считаете, что у вас есть причины для обеспечения такого доступа ко внутреннему состоянию, вы должны детально документировать, как долго возвращаемое значение остается корректным и какие операции делают его недействительным (сравните с гарантиями корректности явных итераторов стандартной библиотеки; см. [C++03]).
Исключения
Иногда классы обязаны предоставить доступ ко внутренним дескрипторам по причинам, связанным с совместимостью, например, для интерфейса со старым кодом или при использовании других систем. Например, std::basic_string предоставляет доступ к своему внутреннему дескриптору посредством функций-членов data и c_str для совместимости с функциями, которые работают с указателями С — но не для того, чтобы хранить эти указатели и пытаться выполнять запись с их помощью! Такие функции доступа "через заднюю дверь" всегда являются злом и должны использоваться очень редко и очень осторожно, а условия корректности возвращаемых ими дескрипторов должны быть точно документированы.
Ссылки
[С++03] §23 • [Dewhurst03] §80 • [Meyers97] #29 • [Saks99] • [Stroustrup00] §7.3 • [Sutter02] §9
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКЧитайте также
Внутренние ссылки
Внутренние ссылки Внутренние ссылки организуют переходы внутри одного HTML-документа. Они применяются, когда на одной странице много текста. Для простоты навигации можно создать ссылки, при щелчке кнопкой мыши на которых пользователь автоматически перейдет к нужной
Не допускайте ошибок
Не допускайте ошибок Ошибки в словах вряд ли порадуют покупателей. Помните об этом при создании продающего текста. Представьте себя на месте клиента. Он открывает письмо и понимает, что вы даже не потрудились грамотно написать слова. Как ему теперь поверить в вашу
3.1.4. Внутренние границы
3.1.4. Внутренние границы В Unix действует предположение о том, что программист знает лучше (чем система). Система не остановит пользователя и не потребует какого-либо подтверждения при выполнении опасных действий с данными, таких как ввод команды rm -rf *. С другой стороны, Unix
Внутренние исключения
Внутренние исключения Вы можете догадываться, что вполне возможно генерировать исключения и во время обработки другого исключения. Например, предположим, что вы обрабатываете CarIsDeadException в рамках конкретного блока catch и в процессе обработки пытаетесь записать след стека
Внутренние устройства
Внутренние устройства Наибольшее число моделей устройств видеоввода реализовано в виде плат. Наверняка даже самым «неискушенным» читателям встречались названия такого рода: «плата в стандарте PCI» (Peripheral Component Interconnect – соединение периферийных компонентов). Сейчас самое
новости: Дела шпионские
новости: Дела шпионские Автор: Киви Берд В последних числах мая в продажу поступила примечательная книга "Шпионское ремесло:Тайная история разведтехнологий ЦРУ от коммунизма до Аль-Каиды".Книга эта хороша уже потому, что вся информация здесь, что называется, из первых
Что общего между гиперлинком и педофилией (по мотивам дела Барретта Брауна) Сергей Голубицкий
Что общего между гиперлинком и педофилией (по мотивам дела Барретта Брауна) Сергей Голубицкий Опубликовано 11 марта 2014 В начале 2012 года я рассказывал читателям «Бизнес-журнала» («Проект Разгром») о сенсационном взломе серверов компании
Глава 5 Дела через Интернет
Глава 5 Дела через Интернет Электронные деньги Что такое виртуальные, или электронные, деньги? В общем случае — это деньги, которые хранятся на электронных носителях. Примечание Мы не будем здесь углубляться в исследование природы этого достаточно нового явления нашей
Внутренние ТВ-тюнеры
Внутренние ТВ-тюнеры Практически все современные внутренние телевизионные тюнеры выпускаются в виде PCI-карт, которые устанавливаются в соответствующий слот материнской платы. Эти тюнеры характеризуются богатством разнообразных сервисных функций, серьезно опережая по
Внутренние DVB-тюнеры
Внутренние DVB-тюнеры Внутренние DVB-тюнеры – это тюнеры, выполненные на плате, подключающейся к компьютеру по шине PCI. Такие тюнеры не имеют своего корпуса, так как находятся внутри корпуса компьютера. Если сравнивать количество моделей внутренних и внешних тюнеров, то
2.1. Внутренние устройства
2.1. Внутренние устройства Компьютер представляет собой модульную конструкцию, в которую входят внутренние и внешние комплектующие. В данном разделе речь пойдет об основных внутренних
Голубятня: Дела кромсальные Сергей Голубицкий
Голубятня: Дела кромсальные Сергей Голубицкий Опубликовано 19 марта 2012 года Сегодня культур-повидлианский спрэд у нас совпадает с софтверным довеском - разговор пойдет о вечном: программах для видеомонтажа. Но только не о монстрах для
Не допускайте случайностей
Не допускайте случайностей Перед съемкой убедитесь, что правильно установлен баланс белого, режим автофокуса, выбрана нужная светочувствительность, а режим съемки не переключился на «макро». Если камера недостаточно хорошо освоена и вы не до конца представляете, что и
Голубятня: Дела кромсальные
Голубятня: Дела кромсальные Автор: Сергей ГолубицкийОпубликовано 19 марта 2012 годаСегодня культур-повидлианский спрэд у нас совпадает с софтверным довеском - разговор пойдет о вечном: программах для видеомонтажа. Но только не о монстрах для профессионалов и тщащихся