Разумно пользуйтесь идиомой Pimpl
Резюме
С++ делает закрытые члены недоступными, но не невидимыми. Там, где это оправдывается получаемыми преимуществами, следует подумать об истинной невидимости, достигаемой применением идиомы Pimpl (указателя на реализацию) для реализации брандмауэров компилятора и повышения сокрытия информации (см. рекомендации 11 и 41).
Обсуждение
Когда имеет смысл создать "брандмауэр компилятора", который полностью изолирует вызывающий код от закрытых частей класса, воспользуйтесь идиомой Pimpl (указателя на реализацию): скройте их за непрозрачным указателем (указатель (предпочтительно подходящий интеллектуальный) на объявленный, но пока не определенный класс). Например: class Map { // ... private: struct Impl; shared_ptr<Impl> pimpl_; }; Дающий название идиоме указатель должен использоваться для хранения всех закрытых членов, как данных, так и закрытых функций-членов. Это позволяет вам вносить произвольные изменения в закрытые детали реализации ваших классов без какой бы то ни было рекомпиляции вызывающего кода. Свобода и независимость — вот отличительные черты рассматриваемой идиомы (см. рекомендацию 41). Примечание: объявляйте указатель на закрытую реализацию, как показано — с использованием двух объявлений. Если вы скомбинируете две строки с предварительным объявлением типа и указателя на него в одну инструкцию struct Impl *pimpl; это будет вполне законно, но изменит смысл объявления: в этом случае Impl находится в охватывающем пространстве имен и не является вложенным типом вашего класса. Имеется как минимум три причины для использования Pimpl, и все они вытекают из различия между доступностью (в состоянии ли вы вызвать или использовать некоторый объект) и видимостью (видим ли этот объект для вас и, таким образом, зависите ли вы от его определения) в С++. В частности, все закрытые члены класса недоступны никому, кроме функций- членов и друзей, но зато видимы всем — любому коду, которому видимо определение класса. Первое следствие этого — потенциально большее время сборки приложения из-за обработки излишних определений типов. Для закрытых данных-членов, хранящихся по значению, и параметров закрытых функций-членов, передаваемых по значению или используемых в видимой реализации функций, типы должны быть определены, даже если они никогда не потребуются в данной единице компиляции. Это может привести к увеличению времени сборки, например: class C { // ... private: AComplicatedType act_; } Заголовочный файл, содержащий определение класса С, должен также включать заголовочный файл, содержащий определение AComplicatedType, который в свою очередь транзитивно включает все заголовочные файлы, которые могут потребоваться для определения AComplicatedType, и т.д. Если заголовочные файлы имеют большие размеры, время компиляции может существенно увеличиться. Второе следствие — создание неоднозначностей и сокрытие имен для кода, который пытается вызвать функцию. Несмотря на то, что закрытая функция-член не может быть вызвана кодом вне ее класса и его друзей, она тем не менее участвует в поиске имен и разрешении перегрузки и тем самым может сделать вызов неоднозначным или некорректным. Перед выполнением проверки доступности С++ выполняет поиск имен и разрешение перегрузки. Из-за этого видимость имеет более высокий приоритет: int Twice(int); // 1
class Calc { public: string Twice(string); // 2
private: char* Twice(char*); // 3
int Test() { return Twice(21); // A: ошибка, функции 2 и 3 не // подходят (могла бы подойти функция 1, но // ее нельзя рассматривать, так она скрыта от // данного кода) } };
Calc с; с.Twice("Hello"); // Б: ошибка, функция 3 // недоступна (могла бы использоваться // функция 2, но она не рассматривается, так // как у функции 3 лучшее соответствие // аргументу) В строке А обходной путь состоит в том, чтобы явно квалифицировать вызов как ::Twice(21) для того, чтобы заставить поиск имен выбрать глобальную функцию. В строке Б обходной путь состоит в добавлении явного преобразования типа с.Twiсе(string("Hellо")) для того, чтобы заставить разрешение перегрузки выбрать соответствующую функцию. Некоторые из таких проблем, связанных с вызовами, можно решить и без применения идиомы Pimpl, например, никогда не используя закрытые перегрузки функций-членов, но не для всех проблем, разрешимых при помощи идиомы Pimpl, можно найти такие обходные пути. Третье следствие влияет на обработку ошибок и безопасность. Рассмотрим пример Widget Тома Каргилла (Tom Cargill): class Widget { // ... public: Widget& operator=(const Widget&); private: T1 t1_; T2 t2_; }; Коротко говоря, мы не можем написать оператор operator=, который обеспечивает строгую гарантию (или хотя бы базовую гарантию), если операции T1 или T2 могут давать необратимые сбои (см. рекомендацию 71). Хорошие новости, однако, состоят в том, что приведенная далее простая трансформация всегда обеспечивает, как минимум, базовую гарантию для безопасного присваивания, и как правило — строгую гарантию, если необходимые операции T1 и T2 (а именно — конструкторы и деструкторы) не имеют побочных эффектов. Для этого следует хранить объекты не по значению, а посредством указателей, предпочтительно спрятанными за единственным указателем на реализацию: class Widget { // ... public: Widget& operator=(const Widget&);
private: struct Impl; shared_ptr<Impl> pimpl_; };
Widget& Widget::operator=( const Widget& ) { shared_ptr<Impl> temp(new Impl( /*...*/ )); // изменяем temp->t1_ и temp->t2_; если какая-то из // операций дает сбой, генерируем исключение, в // противном случае - принимаем внесенные изменения: pimpl_ = temp; return *this; }
Исключения
В то время как вы получаете все преимущества дополнительного уровня косвенности, проблема состоит только в увеличении сложности кода (см. рекомендации 6 и 8).
Ссылки
[Coplien92] §5.5 • [Dewhurst03] §8 • [Lakos96] §6.4.2 • [Meyers97] §34 • [Murray93] §3.3 • [Stroustrup94] §2.10, §24.4.2 • [Sutter00] §23, §26-30 • [Sutter02] §18, §22 • [Sutter04] §16-17
Популярное: Организация как механизм и форма жизни коллектива: Организация не сможет достичь поставленных целей без соответствующей внутренней... Почему человек чувствует себя несчастным?: Для начала определим, что такое несчастье. Несчастьем мы будем считать психологическое состояние... Почему люди поддаются рекламе?: Только не надо искать ответы в качестве или количестве рекламы... ©2015-2024 megaobuchalka.ru Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. (398)
|
Почему 1285321 студент выбрали МегаОбучалку... Система поиска информации Мобильная версия сайта Удобная навигация Нет шокирующей рекламы |