Чи є використання assert () у C ++ поганою практикою?


92

Я схильний додавати багато тверджень до свого коду С ++, щоб полегшити налагодження, не впливаючи на продуктивність збірки випусків. Зараз assertце чистий макрос С, розроблений без урахування механізмів С ++.

C ++, з іншого боку, визначає std::logic_error, що призначено для викиду у випадках, коли в логіці програми є помилка (звідси і назва). Кидання екземпляра може бути просто ідеальною, більш альтернативною C ++ assert.

Проблема полягає в тому, що assertі abortяк завершити програму відразу без виклику деструкторів, тому пропуск очищення, в той час як кидати виключення вручну додає непотрібні витрати часу виконання. Одним із шляхів цього може бути створення власного макросу твердження SAFE_ASSERT, який працює так само, як аналог C, але створює виняток у випадку відмови.

Я можу придумати три думки з цієї проблеми:

  • Дотримуйтесь твердження C. Оскільки програма припиняється негайно, не має значення, чи правильно розгорнуто зміни. Крім того, використання #defines у C ++ так само погано.
  • Викиньте виняток і впіймайте його в main () . Дозволити коду пропускати деструктори в будь-якому стані програми є поганою практикою, і його слід уникати будь-якою ціною, а також заклики до terminate (). Якщо кидаються винятки, їх потрібно зловити.
  • Викиньте виняток і нехай він припинить програму. Виняток із завершенням програми - це нормально, і через NDEBUGце це ніколи не відбудеться у збірці релізу. Зловживання непотрібне і відкриває деталі реалізації внутрішнього коду main().

Чи існує остаточна відповідь на цю проблему? Будь-яке професійне посилання?

Редаговано: Пропуск деструкторів - це, звичайно, не визначена поведінка.


22
Ні, насправді, logic_errorце логічна помилка. Помилка в логіці програми називається помилкою. Ви не вирішуєте помилки, кидаючи винятки.
Р. Мартіньо Фернандес,

4
Твердження, винятки, коди помилок. Кожен з них має абсолютно різний варіант використання, і ви не повинні використовувати той, де потрібен інший.
Kerrek SB

5
Переконайтеся, що ви користуєтеся static_assertтам, де це доречно, якщо у вас є це.
Flexo

4
@trion Я не бачу, як це допомагає. Ви б кинули std::bug?
Р. Мартіньо Фернандес,

3
@trion: Не роби цього. Винятки не стосуються налагодження. Хтось може вловити виняток. Не потрібно турбуватися про UB під час дзвінка std::abort(); це просто підніме сигнал, який призводить до завершення процесу.
Kerrek SB

Відповіді:


73

Твердження цілком доречні в коді С ++. Винятки та інші механізми обробки помилок насправді не призначені для того самого, що і твердження.

Обробка помилок стосується випадків, коли існує можливість відновити або повідомити користувача про помилку. Наприклад, якщо виникає помилка при спробі прочитати вхідний файл, можливо, ви захочете щось з цим зробити. Помилки можуть виникати через помилки, але вони також можуть просто бути відповідним результатом для даного вводу.

Ствердження стосуються таких речей, як перевірка відповідності вимогам API, коли API зазвичай не перевіряється, або перевірка речей, які розробник вважає гарантованими конструкцією. Наприклад, якщо алгоритм вимагає відсортованого вводу, ви зазвичай не перевіряєте це, але у вас може бути твердження, щоб перевірити його, щоб налагодження будувало позначення такого типу помилки. Твердження завжди повинно вказувати на неправильно працюючу програму.


Якщо ви пишете програму, де нечисте вимкнення може спричинити проблему, можливо, ви захочете уникнути тверджень. Невизначена поведінка суворо з точки зору мови С ++ тут ​​не кваліфікується як така проблема, оскільки потрапляння в твердження, ймовірно, вже є результатом невизначеної поведінки або порушення якоїсь іншої вимоги, яка може перешкодити деякій очищенні працювати належним чином.

Крім того, якщо ви реалізуєте твердження з точки зору винятку, тоді воно може потенційно бути схопленим і "обробленим", хоча це суперечить самій меті твердження.


1
Я не зовсім впевнений, що це було зазначено конкретно у відповіді, тому я зазначу це тут: ви не повинні використовувати твердження для будь-чого, що стосується вводу користувача, що не може бути визначене на момент написання коду. Якщо користувач переходить 3замість 1вашого коду, загалом це не повинно викликати твердження. Твердження є лише помилкою програміста, а не помилкою користувача бібліотеки чи програми.
SS Anne

101
  • Твердження призначені для налагодження . Користувач вашого відправленого коду ніколи не повинен їх бачити. Якщо твердження потрапило, ваш код потрібно виправити.

    CWE-617: Досяжне твердження

Продукт містить твердження () або подібний вираз, який може викликати зловмисник, що призводить до виходу програми або іншої поведінки, яка є більш суворою, ніж необхідна.

Хоча твердження корисне для виявлення логічних помилок і зменшення шансів досягнення більш серйозних умов вразливості, воно все одно може призвести до відмови в обслуговуванні.

Наприклад, якщо сервер обробляє декілька одночасних з'єднань, і в одному єдиному з'єднанні виникає assert (), що призводить до відміни всіх інших з'єднань, це доступне твердження, що призводить до відмови в обслуговуванні.

  • Виняток становлять виключні обставини . Якщо такий стикається, користувач не зможе робити те, що хоче, але, можливо, зможе відновити його в іншому місці.

  • Обробка помилок стосується звичайного потоку програм. Наприклад, якщо ви пропонуєте користувачеві вказати номер і отримуєте щось нерозбірливе, це нормально , оскільки введення користувачем не під вашим контролем, і ви завжди повинні вирішувати всі можливі ситуації, як звичайно. (Наприклад, цикл, поки у вас не буде дійсного вводу, між якими буде сказано "Вибачте, спробуйте ще раз".)


1
прийшов шукати це повторне твердження; будь-яка форма твердження, що переходить до виробничого коду, вказує на поганий дизайн та забезпечення якості. Точкою, де викликається твердження, є те, де має бути витончена обробка умови помилки. (Я ніколи не використовую assert's). Що стосується винятків, то я знаю лише єдиний випадок використання, коли ctor може вийти з ладу, а всі інші призначені для нормальної обробки помилок.
slashmais

5
@slashmais: Сентимент похвальний, але якщо ви не доставляєте ідеальний код без помилок, я вважаю, що твердження (навіть таке, що збиває користувача) є кращим перед невизначеною поведінкою. Помилки трапляються в складних системах, і з твердженням у вас є спосіб побачити та діагностувати це там, де це відбувається.
Керрек СБ

@KerrekSB Я вважаю за краще використовувати виняток із твердження. Принаймні код має шанс відкинути невдалу гілку і зробити щось інше корисне. Принаймні, якщо ви використовуєте RAII, усі ваші буфери для відкриття файлів будуть очищені належним чином.
демосвесна

14

Твердження можуть бути використані для перевірки внутрішніх інваріантів реалізації, таких як внутрішній стан до або після виконання якогось методу тощо. Якщо твердження не вдається, це насправді означає, що логіка програми порушена, і ви не можете відновитись після цього. У цьому випадку найкраще, що ви можете зробити, це якомога швидше зламати без передачі винятку користувачеві. Що справді приємно в твердженнях (принаймні в Linux), так це те, що дамп ядра генерується в результаті завершення процесу, і, отже, ви можете легко дослідити трасування стека та змінні. Це набагато корисніше для розуміння логічної помилки, ніж повідомлення про виключення.


У мене схожий підхід. Я використовую твердження щодо логіки, які, мабуть, мають бути коректними локально (наприклад, інваріанти циклу). Виняток становлять випадки, коли логічна помилка була вимушена кодом через нелокальну (зовнішню) ситуацію.
спринт

Якщо твердження не вдається, це означає, що логіка частини програми порушена. Невдале твердження не обов'язково означає, що нічого не можна досягти. Пошкоджений плагін, мабуть, не повинен перервати весь текстовий процесор.
демосвесна

13

Не запускати деструктори через alling abort () - це не визначена поведінка!

Якби це було так, тоді дзвонити std::terminate()теж було б невизначеною поведінкою , і який сенс у цьому буде?

assert() є настільки ж корисним у C ++, як і в C. Твердження не стосуються обробки помилок, вони негайно припиняють програму.


1
Я б сказав, що abort()це означає негайне переривання програми. Ви маєте рацію, хоча твердження не стосуються обробки помилок, проте assert намагається впоратися з помилкою шляхом переривання. Чи не слід замість цього викидати виняток і дозволяти абоненту обробляти помилку, якщо може? Зрештою, абонент має вигіднішу позицію, щоб визначити, чи відмова однієї функції не вартує робити щось інше. Можливо, той, хто телефонує, намагається зробити три непов’язані речі, і все-таки міг би виконати інші дві роботи і просто відкинути цю.
демосвесна

І assertвизначається для виклику abort(коли умова хибна). Що стосується метання винятків, ні, це не завжди доречно. Деякі речі не можуть бути оброблені абонентом. Абонент не може визначити, чи можна відновити помилку логіки у функції сторонніх бібліотек, чи можна виправити пошкоджені дані.
Джонатан Уейклі,

6

ІМХО, твердження стосуються перевірки умов, які, якщо їх порушують, роблять усе інше дурницею. І тому ви не можете від них відновитись, точніше, відновлення не має значення.

Я б згрупував їх у 2 категорії:

  • Гріхи розробника (наприклад, функція ймовірності, яка повертає від’ємні значення):

плаваюча ймовірність () {повернення -1,0; }

стверджувати (ймовірність ()> = 0,0)

  • Машина зламана (наприклад, машина, яка запускає вашу програму, дуже неправильна):

int x = 1;

стверджувати (x> 0);

Це обидва тривіальні приклади, але не надто далекі від реальності. Наприклад, подумайте про наївні алгоритми, які повертають негативні індекси для використання з векторами. Або вбудовані програми в спеціальне обладнання. Точніше тому, що трапляється ш * т .

І якщо є такі помилки у розробці, ви не повинні бути впевнені у будь-якому механізмі відновлення або обробки помилок. Те саме стосується апаратних помилок.


1
твердити (імовірність ()> = 0,0)
Елліот
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.