Використання операторів умовного розгалуження для виявлення помилкового припущення, а також для виведення повідомлень про помилки і завершення виконання програми є настільки поширеним рішенням виникаючих проблем, що C++ вирішив цю справу спростити. І спростив він її за допомогою assert.
Стейтмент assert
Стейтмент assert (або “оператор перевіркового твердження”) в мові C++ — це макрос препроцесора, який обробляє умовний вираз під час виконання. Якщо умовний вираз істинний, то стейтмент assert нічого не робить. Якщо ж умовний вираз помилковий, то виводиться повідомлення про помилку, і виконання програми завершується. Повідомлення про помилку містить помилковий умовний вираз, а також ім’я файлу з кодом і номером рядка з assert. Таким чином, можна легко знайти та ідентифікувати проблему, що дуже допомагає при відлагодженні програм.
Сам assert реалізований в заголовку cassert і часто використовується як для перевірки коректності переданих параметрів функції, так і для перевірки значення, що повертається:
1 2 3 4 5 6 7 8 9 |
#include <cassert> // для assert() int getArrayValue(const std::array<int, 10> &array, int index) { // Припускається, що значення index знаходиться між 0 і 8 assert(index >= 0 && index <= 8); // це рядок 6 в Program.cpp return array[index]; } |
Якщо у вищенаведеній програмі викликати getArrayValue(array, -3);
, то програма виведе наступне повідомлення:
Assertion failed: index >= 0 && index <=8, file C:\\VCProjects\\Program.cpp, line 6
Рекомендується використовувати стейтменти assert. Іноді твердження assert бувають не надто описовими, наприклад:
1 |
assert(found); |
Якщо цей assert спрацює, то ми отримаємо:
Assertion failed: found, file C:\\VCProjects\\Program.cpp, line 42
Але про що це нам говорить? Очевидно, що щось не було знайдено, але що саме? Вам потрібно буде самому пройтися по коду, щоб це визначити.
На щастя, є невеликий трюк, який можна використати для виправлення цієї ситуації. Просто додайте повідомлення в якості рядка C-style разом з логічним оператором І:
1 |
assert(found && "Animal could not be found in database"); |
Як це працює? Рядок C-style завжди приймає значення true
. Тому, якщо found
прийме значення false
, то false && true = false
. Якщо ж found
прийме значення true
, то true && true = true
. Таким чином, рядок C-style взагалі не впливає на обробку твердження.
Проте, якщо assert спрацює, рядок C-style буде включений в повідомлення assert:
Assertion failed: found && "Animal could not be found in database", file C:\\VCProjects\\Program.cpp, line 42
Це дасть додаткове пояснення того, що пішло не так.
NDEBUG
Функція assert() витрачає мало ресурсів на перевірку умови. Крім того, стейтменти assert (в ідеалі) ніколи не повинні зустрічатися в релізному коді (бо ваш код до цього моменту вже повинен бути ретельно протестований). Відповідно, деякі розробники вважають за краще використовувати assert тільки в конфігурації Debug. У мові C++ є можливість відключити всі assert-и в релізному коді, для цього потрібно використати директиву #define NDEBUG
:
1 2 3 |
#define NDEBUG // Всі стейтменти assert будуть проігноровані аж до кінця цього файлу |
Деякі IDE встановлюють NDEBUG
за замовчуванням, як частину параметрів проекту в конфігурації Release.
Зверніть увагу, функція exit() і assert (якщо він спрацьовує) негайно припиняють виконання програми, без можливості виконати подальшу будь-яку очистку (наприклад, закрити файл або базу даних). Через це їх слід використовувати акуратно.
static_assert
У C++11 додали ще один тип assert-а — static_assert. На відміну від assert, який спрацьовує під час виконання програми, static_assert спрацьовує під час компіляції, викликаючи помилку компілятора, якщо умова не є істинною. Якщо умова помилкова, то виводиться діагностичне повідомлення.
Ось приклад використання static_assert для перевірки розмірів певних типів даних:
1 2 3 4 5 6 7 |
static_assert(sizeof(long) == 8, "long must be 8 bytes"); static_assert(sizeof(int) == 4, "int must be 4 bytes"); int main() { return 0; } |
Результат на моєму комп’ютері:
1>C:\ConsoleApplication1\main.cpp(19): error C2338: long must be 8 bytes
Оскільки static_assert обробляється компілятором, то умовна частина static_assert також повинна оброблятися під час компіляції. Оскільки static_assert не обробляється під час виконання програми, то стейтменти static_assert можуть бути розміщені в будь-якому місці коду (навіть в глобальному просторі).
У C++11 діагностичне повідомлення повинно бути обов’язково надано в якості другого параметра. У C++17 надання діагностичного повідомлення є необов’язковим.