Чтение онлайн

на главную - закладки

Жанры

Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ

Майерс Скотт

Шрифт:

FontHandle get const {return f;} // функция явного преобразования

...

};

К сожалению, пользователю придется вызывать get всякий раз при взаимодействии с API:

void changeFontSize(FontHandle f, int newSize); // из C API

Font f(getFont);

int newFontSize;

...

changeFontSize(f.get, newFontSize); //
явное преобразование

// из Font в FontHandle

Некоторые программисты могут посчитать, что каждый раз выполнять явное преобразование настолько обременительно, что вообще откажутся от применения этого класса. В результате возрастет опасность утечки шрифтов, а именно для того, чтобы предотвратить это, и был разработан класс Font.

Альтернативой может стать предоставление классом Font функции неявного преобразования к FontHandle:

class Font {

public:

...

operator FontHandle const // функция неявного преобразования

{return f;}

...

};

Это сделает вызовы C API простыми и естественными:

Font f(getFont);

int newSize;

...

changeFontSize(f, newFontSize); // неявное преобразование из Font

// в FontHandle

Увы, у этого решения есть и оборотная сторона: повышается вероятность ошибок. Например, пользователь может нечаянно создать объект FontHandle, имея в виду Font:

Font f1(getFont);

...

FontHandle f2 = f1; // Ошибка! Предполагалось скопировать объект Font,

// а вместо f1 неявно преобразован в управляемый

// им FontHandle, который и скопирован в f2

Теперь в программе есть FontHandle, управляемый объектом Font f1, однако он же доступен и напрямую, как f2. Это почти всегда нехорошо. Например, если f1 будет уничтожен, шрифт освобождается, и f2 становится «висячей ссылкой».

Решение о том, когда нужно предоставить явное преобразование RAII-объекта к управляемому им ресурсу (посредством функции get), а когда – неявное, зависит от конкретной задачи, для решения которой был спроектирован класс, и условий его применения. Похоже, что лучшее решение – следовать советам правила 18, а именно: делать интерфейсы простыми для правильного применения и трудными – для неправильного. Часто явное преобразование типа функции get – более предпочтительный вариант, поскольку минимизирует шанс получить нежелательное преобразование типов. Однако иногда естественность применения неявного преобразования поможет сделать ваш код чище.

Может показаться, что функции, обеспечивающие доступ к управляемым ресурсам, противоречат принципам инкапсуляции. Верно, но в данном случае это не беда. Дело в том, что 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 – размер массива):

Поделиться:
Популярные книги

Особое назначение

Тесленок Кирилл Геннадьевич
2. Гарем вне закона
Фантастика:
фэнтези
6.89
рейтинг книги
Особое назначение

Её (мой) ребенок

Рам Янка
Любовные романы:
современные любовные романы
6.91
рейтинг книги
Её (мой) ребенок

Я Гордый часть 2

Машуков Тимур
2. Стальные яйца
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Я Гордый часть 2

Адепт. Том второй. Каникулы

Бубела Олег Николаевич
7. Совсем не герой
Фантастика:
фэнтези
попаданцы
9.05
рейтинг книги
Адепт. Том второй. Каникулы

Императорский отбор

Свободина Виктория
Фантастика:
фэнтези
8.56
рейтинг книги
Императорский отбор

Законы Рода. Том 6

Flow Ascold
6. Граф Берестьев
Фантастика:
юмористическое фэнтези
аниме
5.00
рейтинг книги
Законы Рода. Том 6

Начальник милиции. Книга 3

Дамиров Рафаэль
3. Начальник милиции
Фантастика:
попаданцы
альтернативная история
5.00
рейтинг книги
Начальник милиции. Книга 3

Огненный князь 3

Машуков Тимур
3. Багряный восход
Фантастика:
фэнтези
боевая фантастика
попаданцы
5.00
рейтинг книги
Огненный князь 3

Восход. Солнцев. Книга VI

Скабер Артемий
6. Голос Бога
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Восход. Солнцев. Книга VI

Страж. Тетралогия

Пехов Алексей Юрьевич
Страж
Фантастика:
фэнтези
9.11
рейтинг книги
Страж. Тетралогия

Мастер 7

Чащин Валерий
7. Мастер
Фантастика:
фэнтези
боевая фантастика
попаданцы
технофэнтези
аниме
5.00
рейтинг книги
Мастер 7

Волк 5: Лихие 90-е

Киров Никита
5. Волков
Фантастика:
попаданцы
альтернативная история
5.00
рейтинг книги
Волк 5: Лихие 90-е

Сломанная кукла

Рам Янка
5. Серьёзные мальчики в форме
Любовные романы:
современные любовные романы
5.00
рейтинг книги
Сломанная кукла

Идеальный мир для Лекаря 18

Сапфир Олег
18. Лекарь
Фантастика:
юмористическое фэнтези
аниме
5.00
рейтинг книги
Идеальный мир для Лекаря 18