- Любовные романы
- Фантастика и фэнтези
- Ненаучная фантастика
- Ироническое фэнтези
- Научная Фантастика
- Фэнтези
- Ужасы и Мистика
- Боевая фантастика
- Альтернативная история
- Космическая фантастика
- Попаданцы
- Юмористическая фантастика
- Героическая фантастика
- Детективная фантастика
- Социально-психологическая
- Боевое фэнтези
- Русское фэнтези
- Киберпанк
- Романтическая фантастика
- Городская фантастика
- Технофэнтези
- Мистика
- Разная фантастика
- Иностранное фэнтези
- Историческое фэнтези
- LitRPG
- Эпическая фантастика
- Зарубежная фантастика
- Городское фентези
- Космоопера
- Разное фэнтези
- Книги магов
- Любовное фэнтези
- Постапокалипсис
- Бизнес
- Историческая фантастика
- Социально-философская фантастика
- Сказочная фантастика
- Стимпанк
- Романтическое фэнтези
- Ироническая фантастика
- Детективы и Триллеры
- Проза
- Юмор
- Феерия
- Новелла
- Русская классическая проза
- Современная проза
- Повести
- Контркультура
- Русская современная проза
- Историческая проза
- Проза
- Классическая проза
- Советская классическая проза
- О войне
- Зарубежная современная проза
- Рассказы
- Зарубежная классика
- Очерки
- Антисоветская литература
- Магический реализм
- Разное
- Сентиментальная проза
- Афоризмы
- Эссе
- Эпистолярная проза
- Семейный роман/Семейная сага
- Поэзия, Драматургия
- Приключения
- Детская литература
- Загадки
- Книга-игра
- Детская проза
- Детские приключения
- Сказка
- Прочая детская литература
- Детская фантастика
- Детские стихи
- Детская образовательная литература
- Детские остросюжетные
- Учебная литература
- Зарубежные детские книги
- Детский фольклор
- Буквари
- Книги для подростков
- Школьные учебники
- Внеклассное чтение
- Книги для дошкольников
- Детская познавательная и развивающая литература
- Детские детективы
- Домоводство, Дом и семья
- Юмор
- Документальные книги
- Бизнес
- Работа с клиентами
- Тайм-менеджмент
- Кадровый менеджмент
- Экономика
- Менеджмент и кадры
- Управление, подбор персонала
- О бизнесе популярно
- Интернет-бизнес
- Личные финансы
- Делопроизводство, офис
- Маркетинг, PR, реклама
- Поиск работы
- Бизнес
- Банковское дело
- Малый бизнес
- Ценные бумаги и инвестиции
- Краткое содержание
- Бухучет и аудит
- Ораторское искусство / риторика
- Корпоративная культура, бизнес
- Финансы
- Государственное и муниципальное управление
- Менеджмент
- Зарубежная деловая литература
- Продажи
- Переговоры
- Личная эффективность
- Торговля
- Научные и научно-популярные книги
- Биофизика
- География
- Экология
- Биохимия
- Рефераты
- Культурология
- Техническая литература
- История
- Психология
- Медицина
- Прочая научная литература
- Юриспруденция
- Биология
- Политика
- Литературоведение
- Религиоведение
- Научпоп
- Психология, личное
- Математика
- Психотерапия
- Социология
- Воспитание детей, педагогика
- Языкознание
- Беременность, ожидание детей
- Транспорт, военная техника
- Детская психология
- Науки: разное
- Педагогика
- Зарубежная психология
- Иностранные языки
- Филология
- Радиотехника
- Деловая литература
- Физика
- Альтернативная медицина
- Химия
- Государство и право
- Обществознание
- Образовательная литература
- Учебники
- Зоология
- Архитектура
- Науки о космосе
- Ботаника
- Астрология
- Ветеринария
- История Европы
- География
- Зарубежная публицистика
- О животных
- Шпаргалки
- Разная литература
- Зарубежная литература о культуре и искусстве
- Пословицы, поговорки
- Боевые искусства
- Прочее
- Периодические издания
- Фанфик
- Военное
- Цитаты из афоризмов
- Гиды, путеводители
- Литература 19 века
- Зарубежная образовательная литература
- Военная история
- Кино
- Современная литература
- Военная техника, оружие
- Культура и искусство
- Музыка, музыканты
- Газеты и журналы
- Современная зарубежная литература
- Визуальные искусства
- Отраслевые издания
- Шахматы
- Недвижимость
- Великолепные истории
- Музыка, танцы
- Авто и ПДД
- Изобразительное искусство, фотография
- Истории из жизни
- Готические новеллы
- Начинающие авторы
- Спецслужбы
- Подростковая литература
- Зарубежная прикладная литература
- Религия и духовность
- Старинная литература
- Справочная литература
- Компьютеры и Интернет
- Блог
Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ - Скотт Майерс
Шрифт:
Интервал:
Закладка:
Может показаться, что функции, обеспечивающие доступ к управляемым ресурсам, противоречат принципам инкапсуляции. Верно, но в данном случае это не беда. Дело в том, что RAII-классы существуют не для того, чтобы что-то инкапсулировать. Их назначение – гарантировать, что определенное действие (а именно освобождение ресурса) обязательно произойдет. При желании инкапсуляцию ресурса можно реализовать поверх основной функциональности, но это не является необходимым. Более того, некоторые RAII-классы комбинируют истинную инкапсуляцию реализации с отказом от нее в отношении управляемого ресурса. Например, tr1::shared_ptr инкапсулирует подсчет ссылок, но предоставляет простой доступ к управляемому им указателю. Как и большинство хорошо спроектированных классов, он скрывает то, что клиенту не нужно видеть, но обеспечивает доступ к тому, что клиенту необходимо.
Что следует помнить• Программные интерфейсы (API) часто требуют прямого обращения к ресурсам. Именно поэтому каждый RAII-класс должен предоставлять возможность получения доступа к ресурсу, которым он управляет.
• Доступ может быть обеспечен посредством явного либо неявного преобразования. Вообще говоря, явное преобразование безопаснее, но неявное более удобно для пользователей.
Правило 16: Используйте одинаковые формы new и delete
Что неправильно в следующем фрагменте?
std::string *stringArray = new std::string[100];
...
delete stringArray;
На первый взгляд, все в полном порядке – использованию new соответствует применение delete, но кое-что здесь совершенно неверно. Поведение программы непредсказуемо. По меньшей мере, 99 из 100 объектов string, на которые указывает stringArray, вероятно, не будут корректно уничтожены, потому что их деструкторы, скорее всего, так и не вызваны.
При использовании выражения new (когда объект создается динамически путем вызова оператора new) происходят два события. Во-первых, выделяется память (посредством функции operator new, см. правила 49 и 51). Во-вторых, для этой памяти вызывается один или несколько конструкторов. При вызове delete также происходят два события: вызывается один или несколько деструкторов, а затем память возвращается системе (посредством функции operator delete, см. правило 51). Важный вопрос, возникающий в связи с использованием delete, заключается в следующем: сколько объектов следует удалить из памяти? Ответ на него и определяет, сколько деструкторов нужно будет вызвать.
В действительности вопрос гораздо проще: является ли удаляемый указатель указателем на один объект или на массив объектов? Это критичный вопрос, поскольку схема распределения памяти для отдельных объектов существенно отличается от схемы выделения памяти для массивов. В частности, при выделении памяти для массива обычно запоминается его размер, чтобы оператор delete знал, сколько деструкторов вызывать. В памяти, выделенной для отдельного объекта, такая информация не хранится. Различные схемы распределения памяти изображены на рисунке ниже (n – размер массива):
Конечно, это только пример. От компилятора не требуется реализовывать схему именно таким образом, хотя многие так и делают.
Когда вы используете оператор delete для указателя, как он может узнать, что где-то имеется информация о размере массива? Только от вас. Если после delete стоят квадратные скобки, то предполагается, что указатель указывает на массив. В противном случае компилятор считает, что это указатель на отдельный объект:
std::string *stringPtr1 = new std::string;
std::string *stringPtr2 = new std::string[100];
...
delete stringPtr1;
delete[]stringPtr2;
Что произойдет, если использовать форму «[]» с stringPtr1? Результат не определен, но вряд ли он будет приятным. В предположении, что память организована, как в приведенной выше схеме, delete сначала прочитает размер массива, а затем будет вызывать деструкторы, не обращая внимания на тот факт, что память, с которой он работает, не только не является массивом, но даже не содержит объектов того типа, для которых должны быть вызваны деструкторы.
Что случится, если вы не используете форму «[]» для stringPtr2? Неизвестно, но можно предположить, что будет вызван только один деструктор, хотя нужно было вызвать несколько. Более того, это не определено даже для встроенных типов, подобных int, несмотря на то что у них нет деструкторов.
Правило простое: если вы используете [] в выражении new, то должны использовать [] и в соответствующем выражении delete. Если вы не используете [] в new, то не надо использовать его в соответствующем выражении delete.
Это правило особенно важно помнить при написании классов, содержащих указатели на динамически распределенную память, в которых есть несколько конструкторов, поскольку в этом случае вы должны использовать одинаковую форму new во всех конструкторах для инициализации членов-указателей. Если этого не сделать, то как узнать, какую форму delete применить в деструкторе?
Данное правило для тех, кто часто прибегает к использованию typedef, поскольку из него следует, что автор typedef должен документировать, какую форму delete применять для удаления объектов типа, описываемого typedef. Рассмотрим пример:
typedef std::string AddressLines[5]; // адрес человека состоит из 4 строк,
// каждая из которых имеет тип string
Поскольку AddressLines – массив, то следующему применению new
std::string *pal = new AddressLines; // отметим, что “new AddressLines”
// вернет string *, как и
// выражение “new string[4]”
должна соответствовать форма delete для массивов:
delete pal; // не определено!
delete[] pal; // правильно
Чтобы избежать путаницы, старайтесь не примененять typedef для определения типов массивов. Это просто, потому что стандартная библиотека C++ (см. правило 54) включает шаблонные классы string и vector, позволяющие практически полностью избавиться от динамических массивов. Так, в примере выше AddressLines можно было бы определить как вектор строк: vector<string>.
Что следует помнить• Если вы используете [] в выражении new, то должны применять [] и в соответствующем выражении delete. Если вы не используете квадратные скобки [] в выражении new, то не должны использовать их и в соответствующем выражении delete.
Правило 17: Помещение в «интеллектуальный» указатель объекта, вьщеленного с помощью new, лучше располагать в отдельном предложении
Предположим, что есть функция, возвращающая уровень приоритета обработки, и другая функция для выполнения некоторой обработки динамически выделенного объекта Widget в соответствии с этим приоритетом:
int priority();
void processWidgets(std::tr1::shared_ptr<Widget> pw, int priority);
Помня о премудростях применения объектов, управляющих ресурсами (см. правило 13), processWidgets использует «интеллектуальный» указатель (здесь – tr1::shared_ptr) для обработки динамически выделенного объекта. Рассмотрим теперь такой вызов processWidgets:
processWidgets(new Widget, priority());
Стоп, не надо его рассматривать! Он не скомпилируется. Конструктор tr1::shared_ptr, принимающий указатель, объявлен с ключевым словом explicit, поэтому не происходит неявного преобразования из типа указателя, возвращенного выражением «new Widget», в тип tr1::shared_ptr, которого ожидает функция process-Widgets. Однако следующий код компилируется:
processWidgets(std::tr1::shared_ptr<Widget>(new Widget), priority());
Как это ни странно, но несмотря на использование управляющего ресурсами объекта, здесь возможна утечка ресурсов. Разберемся, почему.
Прежде чем компилятор сможет сгенерировать вызов processWidgets, он должен вычислить аргументы, переданные ему в качестве параметров. Второй аргумент – просто вызов функции priority, но первый – (std::tr1::shared_ptr<Widget> (new Widget)) – состоит из двух частей:
• выполнение выражения «new Widget»;
• вызов конструктора tr1::shared_ptr.
Перед тем как произойдет вызов processWidgets, компилятор должен сгенерировать код для решения следующих трех задач:
• вызов priority;
• выполнение «new Widget»;
• вызов конструктора tr1::shared_ptr.
Компиляторам C++ предоставлена определенная свобода в определении порядка выполнения этих операций. (И этим C++ отличается от таких языков, как Java и C#, где параметры функций всегда вычисляются в определенном порядке.) Выражение «new Widget» должно быть выполнено перед вызовом конструктора tr1::shared_ptr, потому что результат этого выражения передается конструктору в качестве аргумента, однако вызов priority может быть выполнен первым, вторым или третьим. Если компилятор решит поставить его на второе место (иногда это позволяет сгенерировать более эффективный код), то мы получим следующую последовательность операций:
1. Выполнение «new Widget».
2. Вызов priority.
3. Вызов конструктора tr1::shared_ptr.
Посмотрим, что случится, если вызов priority возбудит исключение. В этом случае указатель, возвращенный «new Widget», будет потерян, то есть не помещен в объект tr1::shared_ptr, который, как ожидается, должен предотвратить утечку ресурса. Утечка при вызове processWidgets происходит из-за того, что исключение возникает между моментом создания ресурса и моментом помещения его в управляющий объект.

