Скотт Майерс - Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ
- Название:Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ
- Автор:
- Жанр:
- Издательство:Литагент «ДМК»233a80b4-1212-102e-b479-a360f6b39df7
- Год:2006
- Город:Москва
- ISBN:5-94074-304-8
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Скотт Майерс - Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ краткое содержание
Эта книга представляет собой перевод третьего издания американского бестселлера Effective C++ и является руководством по грамотному использованию языка C++. Она поможет сделать ваши программы более понятными, простыми в сопровождении и эффективными. Помимо материала, описывающего общую стратегию проектирования, книга включает в себя главы по программированию с применением шаблонов и по управлению ресурсами, а также множество советов, которые позволят усовершенствовать ваши программы и сделать работу более интересной и творческой. Книга также включает новый материал по принципам обработки исключений, паттернам проектирования и библиотечным средствам.
Издание ориентировано на программистов, знакомых с основами C++ и имеющих навыки его практического применения.
Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
template // как и раньше, включая
class Widget {...}; // функцию-член swap
...
template // свободная функция swap
void swap(Widget& a, // не входит в пространство имен std
Widget& b)
{
a.swap(b);
}
}
Теперь если кто-то вызовет swap для двух объектов Widget, то согласно правилам поиска имен в C++ (а точнее, согласно правилу учета зависимостей от аргументов) будет найдена специфичная для Widget версия в пространстве имен WidgetStuff. А это как раз то, что мы хотим.
Этот подход работает одинаково хорошо для классов и шаблонов классов, поэтому кажется, что именно его и следует всегда использовать. К сожалению, для классов есть причина, по которой надо специализировать std::swap (я опишу ее ниже), поэтому если вы хотите иметь собственную специфичную для класса версию swap, вызываемую в любых контекстах (а вы, без сомнения, хотите), то придется написать и свободную функцию swap в том же пространстве имен, где находится ваш класс, и специализацию std::swap.
Кстати, если вы не пользуетесь пространствам имен, все вышесказанное остается в силе (то есть вам нужна свободная функция swap, которая вызывает функцию-член swap). Но зачем засорять глобальное пространство имен вашими классами, шаблонами, функциями, перечислениями и перечисляемыми константами, определениями типов typedef? Разве вы не имеете понятия о приличиях?
Все, что я написал до сих пор, представляет интерес для авторов функции swap, но стоит посмотреть на ситуацию с точки зрения пользователя. Предположим, вы пишете шаблон функции, в котором хотите поменять значениями два объекта:
template
void doSomething(T& obj1, T& obj2)
{
...
swap(obj1, obj2);
...
}
Какая версия swap должна здесь вызываться? Общая – из пространства std, о существовании которой вы точно знаете; ее специализация главного из std, которая может, существует, а может, нет; или специфичная для класса T, существование которой также под вопросом и которая может находиться в каком-то пространстве имен (но заведомо не в std)? Вам хотелось бы вызвать специфичную для T версию, если она существует, а в противном случае к общей версии из std. Вот как удовлетворить это желание:
template
void doSomething(T& obj1, T& obj2)
{
using std::swap; // сделать std::swap доступной этой функции
...
swap(obj1, obj2); // вызвать лучший вариант swap для объектов типа T
...
}
Когда компилятор встречает вызов swap, он ищет, какую версию вызвать. Правила разрешения имен в C++ гарантируют, что будет найдена любая специфичная для типа T версия в глобальной области видимости или в том же пространстве имен, что и T. (Например, если T – это Widget в пространстве имен Widget-Stuff, компилятор проанализирует аргументы и найдет именно эту версию.) Если же версии swap, специфичной для T, не существует, то компилятор возьмет swap из std благодаря объявлению using, которая делает std::swap видимой. Но даже в этом случае компилятор предпочтет специализацию std::swap для типа T общему шаблону.
Таким образом, заставить компилятор вызвать нужную вам версию swap достаточно просто. Единственное, о чем следует позаботиться, – не квалифицировать вызов именем пространства имен, потому что это влияет на способ выбора функции. Например, если вы напишете вызов следующим образом:
std::swap(obj1, obj2): // неправильный способ вызова swap
то заставите компилятор рассматривать только swap из пространства std (включая все специализации шаблонов), исключив возможность отыскания более подходящей версии, специфичной для типа T, даже если она где-то определена. К сожалению, некоторые программисты по ошибке квалифицируют вызов swap таким образом, поэтому важно предоставлять в своем классе полную специализацию std::swap, тогда даже в таком, неправильно написанном коде специфичная для типа реализация swap окажется доступной. (Подобный код присутствует в некоторых реализациях стандартной библиотеки, поэтому в ваших интересах – сделать все, чтобы он работал эффективно).
Итак, мы обсудили реализацию swap по умолчанию, в виде функции-члена класса, в виде свободной функции и в виде специализации std::swap, а также вызовы swap. Теперь подведем итоги.
Во-первых, если реализация swap по умолчанию обеспечивает приемлемую эффективность для ваших классов или шаблонов классов, то вам не нужно делать ничего. Всякий, кто попытается обменять значения объектов вашего класса, получит версию по умолчанию, и она будет прекрасно работать.
Во-вторых, если реализация по умолчанию swap недостаточно эффективна (что почти всегда означает, что ваш класс или шаблон использует некоторую вариацию идиомы pimpl), сделайте следующее:
1) предоставьте открытую функцию-член, которая эффективно обменивает значения двух объектов вашего типа. По причинам, которые я сейчас объясню, эта функция никогда не должна возбуждать исключений;
2) предоставьте свободную функцию swap в том же пространстве имен, что и ваш класс или шаблон. Пусть она вызывает вашу функцию-член;
3) если вы пишете класс (а не шаблон), специализируйте std::swap для вашего класса. Пусть она также вызывает вашу функцию-член.
Наконец, если вы вызываете swap, убедитесь, что включено using-объявление, которое вводит std::swap в область видимости вашей функции, а затем вызывайте swap без квалификации пространства имен.
Я еще забыл предупредить, что версия функции-члена swap никогда не должна возбуждать исключений. Дело в том, что одно из наиболее частых применений swap – помочь классам (и шаблонам классов) в предоставлении надежных гарантий безопасности исключений. В правиле 29 вы найдете подробную информацию на эту тему, а сейчас лишь подчеркнем, что в основе этого приема лежит предположение о том, что swap, реализованная в виде функции-члена, никогда не возбуждает исключений. Это ограничение касается только функции-члена! Оно не относится к реализации swap в виде свободной функции, поскольку стандартная версия swap по умолчанию основана на конструкторах копирования и операторе присваивания, а этим функциям разрешено возбуждать исключения. Когда вы пишете собственную версию swap, то обычно представляете не просто эффективный способ обмена значений, а такой, при котором не возбуждаются исключения. Общее правил таково: эти две характеристики swap идут рука об руку, потому что высокоэффективные операции обмена всегда основаны на операциях над встроенными типами (такими как указатели, лежащие в основе идиомы pimpl), а операции над встроенными типами никогда не возбуждают исключений.
• Предоставьте функцию-член swap, если std::swap работает с вашим типом неэффективно. Убедитесь, что она не возбуждает исключений.
Читать дальшеИнтервал:
Закладка: