Стандарты программирования на С++. 101 правило и рекомендация | страница 74



, с которым тесно связан вызывающий код.

Исключения

Функции получения и установки значений полезны, но дизайн класса, состоящего практически из одних таких функций, оставляет желать лучшего. Подумайте над тем, требуется ли в таком случае обеспечение абстракции или достаточно ограничиться простой структурой.

Агрегаты значений (известные как структуры в стиле С) просто хранят вместе набор различных данных, но при этом не обеспечивают ни их поведение, ни делают попыток моделирования абстракций или поддержания инвариантов. Такие агрегаты не предназначены для того, чтобы быть абстракциями. Все их данные-члены должны быть открытыми, поскольку эти данные-члены и представляют собой интерфейс. Например, шаблон класса >std::pair используется стандартными контейнерами для объединения двух несвязанных элементов типов >T и >U, и при этом >pair сам по себе не привносит ни поведения, ни каких-либо инвариантов.

Ссылки

[Dewhurst03] §80 • [Henricson97] pg. 105 • [Koenig97] §4 • [Lakos96] §2.2 • [Meyers97] §20 • [Murray93] §2.3 • [Stroustrup00] §10.2.8, §15.3.1.1, §24.4.2-3 • [SuttHysl04a]

42. Не допускайте вмешательства во внутренние дела

Резюме

Избегайте возврата дескрипторов внутренних данных, управляемых вашим классом, чтобы клиенты не могли неконтролируемо изменять состояние вашего объекта, как своего собственного.

Обсуждение

Рассмотрим следующий код:

>class Socket {

>public:

> // ... Конструктор, который открывает handle_,

> // деструктор, который закрывает handle_, и т.д. ...

> int GetHandle() const { return handle_; } // Плохо!

>private:

> int handle_; // дескриптор операционной системы

>};

Сокрытие данных — мощный инструмент абстракции и модульности (см. рекомендации 11 и 41). Однако сокрытие данных при одновременном обеспечении доступа к их дескрипторам обречено на провал, потому что это то же, что и закрыть свою квартиру на замок и положить ключ под коврик у входа или просто оставить его в замке. Вот почему это так.

• В этом случае клиент имеет две возможности реализации функциональности. Он может воспользоваться абстракцией вашего класса (>Socket) либо непосредственно работать с реализацией, на которой основан ваш класс (дескриптор сокета в стиле С). В последнем случае объект оказывается не осведомлен об изменениях, происходящих с ресурсом, которым он, как ему кажется, владеет. Теперь класс не в состоянии надежно обогатить или усовершенствовать функциональность (например, обеспечить прокси, журнализацию, сбор статистики и т.п.), поскольку клиенты могут просто обойти эти возможности реализации, как и любые другие инварианты, которые вы, как вы полагаете, добавили в ваш класс. Это делает невозможной, в частности, корректную обработку возникающих ошибок (см. рекомендацию 70).