Шрифт:
Но нет причин сохранять такое положение дел навсегда. При написании нового кода или модификации существующего тщательно продумывайте способы достижения безопасности исключений. Начните с применения объектов управления ресурсами (см. правило 13). Это предотвратит утечку ресурсов. Затем определите, какую максимальную из трех гарантий безопасности исключений вы можете обеспечить для разрабатываемых функций, оставляя их небезопасными только в том случае, когда вызовы унаследованного кода не оставляют другого выбора. Документируйте ваши решения как для пользователей ваших функций, так и для сопровождения в будущем. Гарантия безопасности исключений функции – это видимая часть ее интерфейса, поэтому вы должны подходить к ней столь же ответственно, как и к другим аспектам интерфейса.
Сорок лет назад код, изобилующий операторами goto, считался вполне приемлемым. Теперь же мы стараемся писать структурированные программы. Двенадцать лет назад глобальные данные ни у кого не вызывали возражений. Теперь мы стремимся данные инкапсулировать. Десять лет назад написание функций без учета влияния исключений было нормой. А сейчас мы боремся за достижение безопасности относительно исключений.
Времена меняются. Мы живем. Мы учимся.
• Безопасные относительно исключений функции не допускают утечки ресурсов и повреждения структур данных, даже в случае возбуждения исключений. Такие функции предоставляют базовую гарантию, строгую гарантию либо гарантию полного отсутствия исключений.
• Строгая гарантия часто может быть реализована посредством копирования и обмена, но предоставлять ее для всех функций непрактично.
• Функция обычно может предоставить гарантию не строже, чем самая слабая гарантия, обеспечиваемая вызываемыми из нее функциями.
Правило 30: Тщательно обдумывайте использование встроенных функций
Встроенные функции – какая замечательная идея! Они выглядят подобно функциям, они работают подобно функциям, они намного лучше макросов (см. правило 2). Их можно вызывать, не опасаясь накладных расходов, связанных с вызовом обычных функций. Чего еще желать?
В действительности вы получаете больше, чем рассчитывали, потому что возможность избежать затрат на вызов функции – это только полдела. Оптимизация, выполняемая компилятором, обычно наиболее эффективна на участке кода, не содержащем вызовов функций. Таким образом, вы даете компилятору возможность оптимизации тела встроенной функции в зависимости от объемлющего контекста. При использовании «обычного» функционального вызова большинство компиляторов такой оптимизации на обычных не выполняют.
Все же давайте не будем слишком увлекаться. В программировании, как и в реальной жизни, не бывает «бесплатных завтраков», и встроенные функции – не исключение. Идея их использования состоит в замене каждого вызова такой функции ее телом. Не нужно быть доктором математических наук, чтобы заметить, что это увеличит общий размер вашего объектного кода. Слишком частое применение встроенных функций на машинах с ограниченной памятью может привести к созданию программы, которая превосходит доступную память. Даже при наличии виртуальной памяти «разбухание» кода, вызванное применением встроенных функций, может привести к дополнительному обмену с диском, уменьшить коэффициент попадания команд в кэш и, следовательно, снизить производительность программы.
С другой стороны, если тело встроенной функции очень короткое, то сгенерированный для нее код может быть короче кода, сгенерированного для вызова функции. В таком случае встраивание функции может привести к уменьшению объектного кода и повышению коэффициента попаданий в кэш!
Имейте в виду, что директива inline – это совет, а не команда компилятору. Совет может быть сформулирован явно или неявно. Неявный способ заключается в определении встроенной функции внутри определения класса:
Такие функции обычно являются функциями-членами, но в правиле 46 объясняется, что функции-друзья тоже могут быть определены внутри класса. В этом случае они также неявно считаются встроенными.
Явно объявить встроенную функцию можно, предварив ее определение ключевым словом inline. Например, вот как обычно реализован стандартный шаблон max (из заголовочного файла <algorithm>):
Тот факт, что max – это шаблон, наводит на мысль, что встроенные функции и шаблоны обычно объявляются в заголовочных файлах. Некоторые программисты делают из этого вывод, что шаблоны функций обязательно должны быть встроенными. Это заключение одновременно неверно и потенциально опасно, поэтому рассмотрим его внимательнее.