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

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

Жанры

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

Майерс Скотт

Шрифт:

getRidOfInvestment); // чистильщика. о static_cast см.

// в правиле 27

Это значит, что код, реализующий createInvestment, который должен возвратить tr1::shared_ptr с getRidOfInvestment в качества чистильщика, будет выглядеть примерно так:

std::tr1::shared_ptr<Investment> createInvestment

{

std::tr1::shared_ptr<Investment> retVal(static_cast<Investment*>(0),

getRidOfInvestment);

retVal = ...; // retVal должен указывать

//
на корректный объект

return retVal;

}

Конечно, если указатель, которым должен управлять pInv, можно было бы определить до создания pInv, то лучше было бы передать его конструктору pInv вместо инициализации pInv нулем с последующим присваиванием значения (см. правило 26).

Особенно симпатичное свойство tr1::shared_ptr заключается в том, что он автоматически использует определенного пользователем чистильщика, чтобы избежать другой потенциальной ошибки пользователя – «проблемы нескольких DLL». Она возникает, если объект создается оператором new в одной динамически скомпонованной библиотеке (DLL), а удаляется оператором delete в другой. На многих платформах в такой ситуации возникает ошибка во время исполнения. tr1::shared_ptr решает эту проблемы, поскольку его чистильщик по умолчанию использует delete из той же самой DLL, где был создан tr1::shared_ptr. Это значит, например, что если класс Stock является производным от Investment и функция createInvestment реализована следующим образом:

std::tr1::shared_ptr<Investment> createInvestment

{

return std::tr1::shared_ptr<Investment>(new Stock);

}

то возвращенный ей объект tr1::shared_ptr можно передавать между разными DLL без риска столкнуться с описанной выше проблемой. Объект tr1::shared_ptr, указывающий на Stock, «помнит», из какой DLL должен быть вызван delete, когда счетчик ссылок на Stock достигнет нуля.

Впрочем, этот правило не о tr1::shared_ptr, а о том, как делать интерфейсы легкими для правильного использования и трудными – для неправильного. Но класс tr1::shared_ptr дает настолько простой способ избежать некоторых клиентских ошибок, что на нем стоило остановиться. Наиболее распространенная реализация tr1::shared_ptr находится в библиотеке Boost (см. правило 55). Размер объекта shared_ptr из Boost вдвое больше размера обычного указателя, в нем динамически выделяется память для служебных целей и данных, относящихся к чистильщику, используется вызов виртуальной функции для обращения к чистильщику, производится синхронизация потоков при изменении значения счетчика ссылок в многопоточной среде. (Вы можете отключить поддержку многопоточности, определив символ препроцессора.) Короче говоря, этот интеллектуальный указатель по размеру больше обычного, работает медленнее и использует дополнительную динамически выделяемую память. Но во многих приложениях эти дополнительные затраты времени исполнения будут незаметны, зато уменьшение числа ошибок пользователей заметят все.

Что следует помнить

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

• Для обеспечения корректного использования интерфейсы должны быть согласованы и совместимы со встроенными типами.

• Для предотвращения ошибок применяют следующие способы: создание новых типов, ограничение допустимых операций над этими типами, ограничение допустимых значений, а также освобождение пользователя от обязанностей по управлению ресурсами.

• Класс tr1::shared_ptr поддерживает пользовательские функции-чистильщики. Это снимает «проблему нескольких DLL» и может быть, в частности, использовано для автоматического освобождения мьютекса (см. правило 14).

Правило 19: Рассматривайте проектирование класса как проектирование типа

В C++, как и в других объектно-ориентированных языках программирования, при определении нового класса определяется новый тип. Потому большую часть времени вы как разработчик C++ будете тратить на совершенствование вашей системы типов. Это значит, что вы – не просто разработчик классов, но еще и разработчик типов. Перегруженные функции и операторы, управление распределением и освобождением памяти, определение инициализации и порядка уничтожения объектов – все это находится в ваших руках. Поэтому вы должны подходить к проектированию классов так, как разработчики языка подходят к проектированию встроенных типов.

Проектирование хороших классов – ответственная работа, и этим все сказано. Хорошие типы имеют естественный синтаксис, интуитивно воспринимаемую семантику и одну или более эффективных реализаций. В C++ плохо спланированное определение класса может сделать невозможным достижение любой из этих целей. Даже характеристики производительности функций-членов класса могут зависеть от того, как они объявлены.

Итак, как же проектировать эффективные классы? Прежде всего вы должны понимать, с чем имеете дело. Проектирование почти любого класса ставит перед разработчиком вопросы, ответы на которые часто ограничивают спектр возможных решений:

Как должны создаваться и уничтожаться объекты нового типа? От ответа на этот вопрос зависит дизайн конструкторов и деструкторов, а равно функций распределения и освобождения памяти (оператор new, оператор new[], оператор delete и оператор delete[] – см. главу 8), если вы собираетесь их переопределить.

Чем должна отличаться инициализация объекта от присваивания значений? Ответ на этот вопрос определяет разницу в поведении между конструкторами и операторами присваивания. Важно не путать инициализацию с присваиванием, потому что им соответствуют разные вызовы функций (см. правило 4).

Что означает для объектов нового типа быть переданными по значению? Помните, что конструктор копирования определяет реализацию передачи по значению для данного типа.

Каковы ограничения на допустимые значения вашего нового типа? Обычно только некоторые комбинации значений данных-членов класса являются правильными. Эти комбинации определяют инварианты, которые должен поддерживать класс. А инварианты уже диктуют, как следует контролировать ошибки в функциях-членах, в особенности в конструкторах, операторах присваивания и функциях установки значений («setter» functions). Могут быть также затронуты исключения, которые возбуждают ваши функции, и спецификации этих исключений.

Укладывается ли ваш новый тип в граф наследования? Наследуя свои классы от других, вы должны следовать ограничениям, налагаемым базовыми классами. В частности, нужно учитывать, как объявлены в них функции-члены: виртуальными или нет (см. правила 34 и 36). Если вы хотите, чтобы вашему классу могли наследовать другие, то нужно тщательно продумать, какие функции объявить виртуальными; в особенности это относится к деструктору (см. правило 7).

Какие варианты преобразования типов допустимы для вашего нового типа? Ваш тип существует в море других типов, поэтому должны ли быть предусмотрены варианты преобразования между вашим типом и другими? Если вы хотите разрешить неявное преобразование объекта типа T1 в объект типа T2, придется либо написать функцию преобразования в классе T1 (то есть operator T2), либо неявный конструктор в классе T2, который может быть вызван с единственным аргументом. Если же вы хотите разрешить только явные преобразования, то нужно будет написать специальные функции, но ни в коем случае не делать их операторами преобразования или не-explicit конструкторами с одним аргументом. (Примеры явных и неявных функций преобразования приведены в правиле 15.)

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

Смерть может танцевать 4

Вальтер Макс
4. Безликий
Фантастика:
боевая фантастика
5.85
рейтинг книги
Смерть может танцевать 4

Бездомыш. Предземье

Рымин Андрей Олегович
3. К Вершине
Фантастика:
фэнтези
попаданцы
рпг
5.00
рейтинг книги
Бездомыш. Предземье

Я не дам тебе развод

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

Внешники

Кожевников Павел
Вселенная S-T-I-K-S
Фантастика:
боевая фантастика
попаданцы
5.00
рейтинг книги
Внешники

Тринадцатый II

NikL
2. Видящий смерть
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Тринадцатый II

Возвышение Меркурия. Книга 12

Кронос Александр
12. Меркурий
Фантастика:
героическая фантастика
попаданцы
аниме
5.00
рейтинг книги
Возвышение Меркурия. Книга 12

Черный Маг Императора 5

Герда Александр
5. Черный маг императора
Фантастика:
юмористическое фэнтези
попаданцы
аниме
5.00
рейтинг книги
Черный Маг Императора 5

Паладин из прошлого тысячелетия

Еслер Андрей
1. Соприкосновение миров
Фантастика:
боевая фантастика
попаданцы
6.25
рейтинг книги
Паладин из прошлого тысячелетия

Он тебя не любит(?)

Тоцка Тала
Любовные романы:
современные любовные романы
7.46
рейтинг книги
Он тебя не любит(?)

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

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

Гарем вне закона 18+

Тесленок Кирилл Геннадьевич
1. Гарем вне закона
Фантастика:
фэнтези
юмористическая фантастика
6.73
рейтинг книги
Гарем вне закона 18+

Счастье быть нужным

Арниева Юлия
Любовные романы:
любовно-фантастические романы
5.25
рейтинг книги
Счастье быть нужным

Девяностые приближаются

Иванов Дмитрий
3. Девяностые
Фантастика:
попаданцы
альтернативная история
7.33
рейтинг книги
Девяностые приближаются

Мастер 2

Чащин Валерий
2. Мастер
Фантастика:
фэнтези
городское фэнтези
попаданцы
технофэнтези
4.50
рейтинг книги
Мастер 2