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

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

Жанры

Эффективное использование 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 – размер массива):

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

Инцел на службе демоницы 1 и 2: Секса будет много

Блум М.
Инцел на службе демоницы
Фантастика:
фэнтези
5.25
рейтинг книги
Инцел на службе демоницы 1 и 2: Секса будет много

Весь цикл «Десантник на престоле». Шесть книг

Ланцов Михаил Алексеевич
Десантник на престоле
Фантастика:
альтернативная история
8.38
рейтинг книги
Весь цикл «Десантник на престоле». Шесть книг

Предатель. Цена ошибки

Кучер Ая
Измена
Любовные романы:
современные любовные романы
5.75
рейтинг книги
Предатель. Цена ошибки

Жандарм 2

Семин Никита
2. Жандарм
Фантастика:
попаданцы
альтернативная история
аниме
5.00
рейтинг книги
Жандарм 2

Прогрессор поневоле

Распопов Дмитрий Викторович
2. Фараон
Фантастика:
попаданцы
альтернативная история
5.00
рейтинг книги
Прогрессор поневоле

Всемирная энциклопедия афоризмов. Собрание мудрости всех народов и времен

Агеева Елена А.
Документальная литература:
публицистика
5.40
рейтинг книги
Всемирная энциклопедия афоризмов. Собрание мудрости всех народов и времен

Истребитель. Ас из будущего

Корчевский Юрий Григорьевич
Фантастика:
боевая фантастика
попаданцы
альтернативная история
5.25
рейтинг книги
Истребитель. Ас из будущего

Очешуеть! Я - жена дракона?!

Амеличева Елена
Фантастика:
юмористическая фантастика
5.43
рейтинг книги
Очешуеть! Я - жена дракона?!

Отмороженный 4.0

Гарцевич Евгений Александрович
4. Отмороженный
Фантастика:
боевая фантастика
постапокалипсис
рпг
5.00
рейтинг книги
Отмороженный 4.0

Треугольная шляпа. Пепита Хименес. Донья Перфекта. Кровь и песок.

Бласко Висенте Ибаньес
65. Библиотека всемирной литературы
Проза:
классическая проза
5.00
рейтинг книги
Треугольная шляпа.
Пепита Хименес.
Донья Перфекта.
Кровь и песок.

Измена. Жизнь заново

Верди Алиса
1. Измены
Любовные романы:
современные любовные романы
5.00
рейтинг книги
Измена. Жизнь заново

Три `Д` для миллиардера. Свадебный салон

Тоцка Тала
Любовные романы:
современные любовные романы
короткие любовные романы
7.14
рейтинг книги
Три `Д` для миллиардера. Свадебный салон

Бастард Императора. Том 5

Орлов Андрей Юрьевич
5. Бастард Императора
Фантастика:
попаданцы
аниме
фэнтези
5.00
рейтинг книги
Бастард Императора. Том 5

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

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