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

Шрифт
Интервал

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


>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 продолжают играть важную роль в управлении компиляцией. Пока еще не время отказываться от препроцессора, но определенно стоит задуматься, как избавиться от него в дальнейшем.

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

• Для простых констант директиве #define следует предпочесть константные объекты и перечисления (enum).

• Вместо имитирующих функции макросов, определенных через #define, лучше применять встроенные функции.

Правило 3: Везде, где только можно используйте const

Замечательное свойство модификатора const состоит в том, что он накладывает определенное семантическое ограничение: данный объект не должен модифицироваться, – и компилятор будет проводить это ограничение в жизнь. const позволяет указать компилятору и программистам, что определенная величина должна оставаться неизменной. Во всех подобных случаях вы должны обозначить это явным образом, призывая себе на помощь компилятор и гарантируя тем самым, что ограничение не будет нарушено.

Ключевое слово const удивительно многосторонне. Вне классов вы можете использовать его для определения констант в глобальной области или в пространстве имен (см. правило 2), а также для статических объектов (внутри файла, функции или блока). Внутри классов допустимо применять его как для статических, так и для нестатических данных-членов. Для указателей можно специфицировать, должен ли быть константным сам указатель, данные, на которые он указывает, либо и то, и другое (или ни то, ни другое):


>char greeting[] = “Hello”;

>char *p = greeting; // неконстантный указатель,

>// неконстантные данные

>const char *p = greeting; // неконстантный указатель,

>// константные данные

>char * const p = greeting; // константный указатель,

>// неконстантные данные

>const char * const p = greeting; // константный указатель,

>// константные данные


Этот синтаксис не так страшен, как может показаться. Если слово const появляется слева от звездочки, константным является то, на что указывает указатель; если справа, то сам указатель является константным. Наконец, если же слово const появляется с обеих сторон, то константно и то, и другое.

Когда то, на что указывается, – константа, некоторые программисты ставят const перед идентификатором типа. Другие – после идентификатора типа, но перед звездочкой. Семантической разницы здесь нет, поэтому следующие функции принимают параметр одного и того же типа:


>void f1(const Widget *pw); // f1 принимает указатель на

>// константный объект Widget

>void f1(Widget const *pw); // то же самое делает f2


Поскольку в реальном коде встречаются обе формы, следует привыкать и к той, и к другой.

Итераторы STL смоделированы на основе указателей, поэтому iterator ведет себя почти как указатель T*. Объявление const-итератора подобно объявлению const-указателя (то есть записи T* const): итератор не может начать указывать на что-то другое, но то, на что он указывает, может быть модифицировано. Если вы хотите иметь итератор, который указывал бы на нечто, что запрещено модифицировать (то есть STL-аналог указателя const T*), то вам понадобится константный итератор:


>std::vector vec;

>...

>const std::vector::iterator iter = // iter работает как T* const

>vec.begin();

>*iter = 10; // Ok, изменяется то, на что

>// указывает iter

>++iter; // ошибка! iter константный


Рекомендуем почитать
Pro Git

Разработчику часто требуется много сторонних инструментов, чтобы создавать и поддерживать проект. Система Git — один из таких инструментов и используется для контроля промежуточных версий вашего приложения, позволяя вам исправлять ошибки, откатывать к старой версии, разрабатывать проект в команде и сливать его потом. В книге вы узнаете об основах работы с Git: установка, ключевые команды, gitHub и многое другое.В книге рассматриваются следующие темы:основы Git;ветвление в Git;Git на сервере;распределённый Git;GitHub;инструменты Git;настройка Git;Git и другие системы контроля версий.


Java 7

Рассмотрено все необходимое для разработки, компиляции, отладки и запуска приложений Java. Изложены практические приемы использования как традиционных, так и новейших конструкций объектно-ориентированного языка Java, графической библиотеки классов Swing, расширенной библиотеки Java 2D, работа со звуком, печать, способы русификации программ. Приведено полное описание нововведений Java SE 7: двоичная запись чисел, строковые варианты разветвлений, "ромбовидный оператор", NIO2, новые средства многопоточности и др.


MFC и OpenGL

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Симуляция частичной специализации

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Обработка событий в С++

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Питон — модули, пакеты, классы, экземпляры

Python - объектно-ориентированный язык сверхвысокого уровня. Python, в отличии от Java, не требует исключительно объектной ориентированности, но классы в Python так просто изучить и так удобно использовать, что даже новые и неискушенные пользователи быстро переходят на ОО-подход.