В отличие от других функций, встраиваемые функции и функции constexpr
могут быть определены в программе несколько раз. В конце концов, чтобы встроить код, компилятор нуждается в определении, а не только в объявлении. Однако все определения конкретной встраиваемой функции и функции constexpr
должны совпадать точно. В результате встраиваемые функции и функции constexpr
обычно определяют в заголовках.
Упражнение 6.43. Какое из следующих объявлений и определений имеет смысл поместить в файл заголовка, а какой — в текст файла исходного кода? Объясните почему.
(a) inline bool eq(const BigInt&, const BigInt&) {...}
(b) void putValues(int *arr, int size);
Упражнение 6.44. Перепишите функцию isShorter()
из раздела 6.2.2 как встраиваемую.
Упражнение 6.45. Пересмотрите функции, написанные для предыдущих упражнений, и решите, должны ли они быть определены как встраиваемые. Если да, то сделайте это. В противном случае объясните, почему они не должны быть встраиваемыми.
Упражнение 6.46. Возможно ли определить функцию isShorter
как constexpr
? Если да, то сделайте это. В противном случае объясните, почему нет.
6.5.3. Помощь в отладке
Для условного выполнения отладочного кода программисты С++ иногда используют подход, подобный защите заголовка (см. раздел 2.6.3). Идея в том, что программа будет содержать отладочный код, который выполняется только во время разработки программы. Когда приложение закончено и готово к выпуску, отладочный код исключается. Этот подход подразумевает использование двух средств препроцессора: assert
и NDEBUG
.
assert
assert
— это assert
получает одно выражение и использует его как условие:
assert(выражение);
Если результат выражения ложь (т.е. нуль), то макрос assert
выдает сообщение и закрывает программу. Если результат выражения — истина (т.е. он отличен от нуля), то макрос assert
не делает ничего.
Действие макроса
препроцессора подобно вызову функции. Макрос assert
получает одно
Макрос assert
определен в заголовке cassert
. Как уже упоминалось, относящиеся к препроцессору имена обрабатывает препроцессор, а не компилятор (см. раздел 2.3.2). В результате такие имена можно использовать непосредственно, без объявления using
. Таким образом, используется имя assert
, а не std::assert
, кроме того, для него не предоставляется объявление using
.
Макрос assert
зачастую используется для проверки "недопустимых" условий. Например, программа обработки вводимого текста могла бы проверять, что все вводимые слова длиннее некоего порогового значения. Эта программа могла бы содержать такой оператор:
assert(word.size() > threshold);
NDEBUG
Поведение макроса assert
зависит от состояния переменной препроцессора NDEBUG
. Если переменная NDEBUG
определена, макрос assert
ничего не делает. По умолчанию переменная NDEBUG
не определена, поэтому по умолчанию макрос assert
выполняет проверку.
Отладку можно "выключить", предоставив директиву #define
, определяющую переменную NDEBUG
. В качестве альтернативы большинство компиляторов предоставляет параметр командной строки, позволяющий определять переменные препроцессора:
$ CC -D NDEBUG main.С # use /D with the Microsoft compiler
Результат будет тот же, что и при наличии строки #define NDEBUG
в начале файла main.С
.
Когда переменная NDEBUG
определена, программа во время выполнения избегает дополнительных затрат на проверку различных условий. Самих проверок во время выполнения, конечно, тоже не будет. Поэтому макрос assert
следует использовать только для проверки того, что действительно недопустимо. Это может быть полезно при отладке программы, но не должно использоваться для замены логических проверок времени выполнения или проверки ошибок, которые должна осуществлять программа.
В дополнение к макросу assert
можно написать собственный отладочный код, выполняющийся в зависимости от переменной NDEBUG
. Если переменная NDEBUG не определена, код между директивами #ifndef
и #endif
выполняется, а в противном случае игнорируется:
void print(const int ia[], size_t size) {
#ifndef NDEBUG
//
//