Скотт Майерс - Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ
- Название:Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ
- Автор:
- Жанр:
- Издательство:Литагент «ДМК»233a80b4-1212-102e-b479-a360f6b39df7
- Год:2006
- Город:Москва
- ISBN:5-94074-304-8
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Скотт Майерс - Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ краткое содержание
Эта книга представляет собой перевод третьего издания американского бестселлера Effective C++ и является руководством по грамотному использованию языка C++. Она поможет сделать ваши программы более понятными, простыми в сопровождении и эффективными. Помимо материала, описывающего общую стратегию проектирования, книга включает в себя главы по программированию с применением шаблонов и по управлению ресурсами, а также множество советов, которые позволят усовершенствовать ваши программы и сделать работу более интересной и творческой. Книга также включает новый материал по принципам обработки исключений, паттернам проектирования и библиотечным средствам.
Издание ориентировано на программистов, знакомых с основами C++ и имеющих навыки его практического применения.
Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
class GamePlayer {
private:
static const int NumTurns = 5; // объявление константы
int scores[NumTurns]; // использование константы
...
};
То, что вы видите выше, – это объявление NumTurns, а не ее определение. Обычно C++ требует, чтобы вы представляли определение для всего, что используете, но объявленные в классе константы, которые являются статическими и имеют встроенный тип (то есть целые, символьные, булевские) – это исключение из правил. До тех пор пока вы не пытаетесь получить адрес такой константы, можете объявлять и использовать ее без предоставления определения. Если же вам нужно получить адрес либо если ваш компилятор настаивает на наличии определения, то можете написать что-то подобное:
const int GamePlayer::NumTurns; // определение NumTurns; см. ниже,
// почему не указывается значение
Поместите этот код в файл реализации, а не в заголовочный файл. Поскольку начальное значение константы класса представлено там, где она объявлена (то есть NumTurns инициализировано значением 5 при объявлении), то в точке определения задавать начальное значение не требуется.
Отметим, кстати, что нет возможности объявить в классе константу посредством #define, потому что #define не учитывает области действия. Как только макрос определен, он остается в силе для всей оставшейся части компилируемого кода (если только где-то ниже не встретится #undef). Это значит, что директива #define неприменима не только для объявления констант в классе, но вообще не может быть использована для обеспечения какой бы то ни было инкапсуляции, то есть придать смысл выражению «private #define» невозможно. В то же время константные данные-члены могут быть инкапсулированы, примером может служить NumTurns.
Старые компиляторы могут не поддерживать показанный выше синтаксис, так как в более ранних версиях языка было запрещено задавать значения статических членов класса во время объявления. Более того, инициализация в классе допускалась только для целых типов и для констант. Если вышеприведенный синтаксис не работает, то начальное значение следует задавать в определении:
class CostEstimate {
private:
static const double FudgeFactor; // объявление статической константы
... // класса – помещается в файл заголовка
};
const double // определение статической константы
CostEstimate::FudgeFactor = 1.35; // класса – помещается в файл реализации
Обычно ничего больше и не требуется. Единственное исключение обнаруживается тогда, когда для компиляции класса необходима константа. Например, при объявлении массива GamePlayer::scores компилятору нужно знать размер массива. Чтобы работать с компилятором, ошибочно запрещающим инициализировать статические целые константы внутри класса, можно воспользоваться способом, известным под названием «трюка с перечислением». Он основан на том, что переменные перечисляемого типа можно использовать там, где ожидаются значения типа int, поэтому GamePlayer можно определить так:
class GamePlayer {
private:
enum ( NumTurns = 5 }; // “трюк с перечислением” – делает из
// NumTurns символ со значением 5
int scores[NumTurns]; // нормально
...
};
Этот прием стоит знать по нескольким причинам. Во-первых, поведение «трюка с перечислением» в некоторых отношениях более похоже на #define, чем на константу, а иногда это как раз то, что нужно. Например, можно получить адрес константы, но нельзя получить адрес перечисления, как нельзя получить и адрес #define. Если вы хотите запретить получать адрес или ссылку на какую-нибудь целую константу, то применение enum – хороший способ наложить такое ограничение. (Подробнее о поддержке проектных ограничений с помощью приемов кодирования можно узнать из правила 18). К тому же, хотя хорошие компиляторы не выделяют память для константных объектов целых типов (если только вы не создаете указателя или ссылки на объект), менее изощренные могут так поступать, а вам это, возможно, ни к чему. Как и #define, перечисления никогда не станут причиной подобного нежелательного распределения памяти.
Вторая причина знать о «трюке с перечислением» чисто прагматическая. Он используется в очень многих программах, поэтому нужно уметь распознавать этот трюк, когда вы с ним сталкиваетесь. Вообще говоря, этот прием – фундаментальная техника, применяемая при метапрограммировании шаблонов (см. правило 48).
Вернемся к препроцессору. Другой частый случай неправильного использования директивы #define – создание макросов, которые выглядят как функции, но не обременены накладными расходов, связанными с вызовом функций. Ниже представлен макрос, который вызывает некоторую функцию f c аргументом, равным максимальному из двух значений:
// вызвать f, передав ей максимум из a и b
#define CALL_WITH_MAX(a,b) f((a) > (b) ? (a) : (b))
В этой строчке содержится так много недостатков, что даже не совсем понятно, с какого начать.
Всякий раз при написании подобного макроса вы должны помнить о том, что все аргументы следует заключать в скобки. В противном случае вы рискуете столкнуться с проблемой, когда кто-нибудь вызовет его с выражением в качестве аргумента. Но даже если вы сделаете все правильно, посмотрите, какие странные вещи могут произойти:
int a = 5, b = 0;
CALL_WITH_MAX(++a, b); // a увеличивается дважды
CALL_WITH_MAX(++a, b+10); // a увеличивается один раз
Происходящее внутри max зависит от того, с чем она сравнивается!
К счастью, вы нет нужды мириться с поведением, так сильно противоречащим привычной логике. Существует метод, позволяющий добиться такой же эффективности, как при использовании препроцессора. Но при этом обеспечивается как предсказуемость поведения, так и контроль типов аргументов (что характерно для обычных функций). Этот результат достигается применением шаблона встроенной (inline) функции (см. правило 30):
template
inline void callWithMax(const T& a, const T& b) // Поскольку мы не знаем,
{ // что есть T, то передаем
f(a > b ? a : b); // его по ссылке на const -
} // см. параграф 20
Этот шаблон генерирует целое семейство функций, каждая из которых принимает два аргумента одного и того же типа и вызывает f с наибольшим из них. Нет необходимости заключать параметры в скобки внутри тела функции, не нужно заботиться о многократном вычислении параметров и т. д. Более того, поскольку callWithMax – настоящая функция, на нее распространяются правила областей действия и контроля доступа. Например, можно говорить о встроенной функции, являющейся закрытым членом класса. Описать нечто подобное с помощью макроса невозможно.
Наличие const, enum и inline резко снижает потребность в препроцессоре (особенно это относится к #define), но не устраняет ее полностью. Директива #include остается существенной, а #ifdef/#ifndef продолжают играть важную роль в управлении компиляцией. Пока еще не время отказываться от препроцессора, но определенно стоит задуматься, как избавиться от него в дальнейшем.
Читать дальшеИнтервал:
Закладка: