Как я могу assert () без использования abort ()?

Если я использую assert() и утверждение не выполняется, assert() вызывает abort(), внезапно завершая выполняющуюся программу. Я не могу себе этого позволить в своем производственном коде. Есть ли способ утверждать во время выполнения, но при этом иметь возможность перехватывать неудачные утверждения, чтобы у меня был шанс изящно их обработать?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
26
0
21 346
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Ответ принят как подходящий

Да, на самом деле есть. Вам нужно будет написать настраиваемую функцию assert самостоятельно, поскольку assert() в C++ - это в точности assert() из C, со встроенной «функцией» abort(). К счастью, это на удивление просто.

Assert.hh

template <typename X, typename A>
inline void Assert(A assertion)
{
    if ( !assertion ) throw X();
}

Вышеупомянутая функция вызовет исключение, если предикат не выполняется. Тогда у вас будет возможность поймать исключение. Если вы не поймаете исключение, будет вызван terminate(), который завершит программу аналогично abort().

Вы можете спросить, а как насчет оптимизации утверждения, когда мы создаем для продакшена. В этом случае вы можете определить константы, которые будут обозначать, что вы создаете для производства, а затем ссылаться на константу при использовании Assert().

debug.hh

#ifdef NDEBUG
    const bool CHECK_WRONG = false;
#else
    const bool CHECK_WRONG = true;
#endif

main.cc

#include<iostream>

struct Wrong { };

int main()
{
    try {
        Assert<Wrong>(!CHECK_WRONG || 2 + 2 == 5);
        std::cout << "I can go to sleep now.\n";
    }
    catch( Wrong e ) {
        std::cerr << "Someone is wrong on the internet!\n";
    }

    return 0;
}

Если CHECK_WRONG является константой, тогда вызов Assert() будет скомпилирован в производственной среде, даже если утверждение не является константным выражением. Есть небольшой недостаток в том, что, ссылаясь на CHECK_WRONG, мы вводим немного больше. Но взамен мы получаем преимущество в том, что мы можем классифицировать различные группы утверждений и включать и отключать каждую из них по своему усмотрению. Так, например, мы могли бы определить группу утверждений, которые мы хотим включить даже в производственном коде, а затем определить группу утверждений, которые мы хотим видеть только в разрабатываемых сборках.

Функция Assert() эквивалентна вводу

if ( !assertion ) throw X();

но это ясно указывает на намерение программиста: сделать утверждение. Утверждения также легче найти с помощью этого подхода, как и обычные assert().

Дополнительные сведения об этой технике см. В книге Бьярна Страуструпа «Язык программирования C++ 3e», раздел 24.3.7.2.

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

Greg Rogers 03.10.2008 23:47

В чем добавленная стоимость шаблонизации аргумента assert вместо того, чтобы сделать его int? В любом случае его нужно будет преобразовать в int.

JohnMcG 04.10.2008 01:39

@Greg Rogers Bjarne Stroustrup предлагает решение проблемы, о которой вы упомянули: вы можете добавить настраиваемый объект, который вы хотите использовать, в качестве дополнительного параметра Assert (). Обратной стороной является то, что Assert () больше не будет выглядеть как assert () из-за дополнительного параметра.

wilhelmtell 04.10.2008 02:53

@JohnMcG: параметр шаблона дает мне дополнительную гибкость. вы можете передать все, что угодно функции Assert (), для которой определен operator! () и которая возвращает что-то, что можно преобразовать в bool. работают логические значения, целые числа и некоторые настраиваемые объекты.

wilhelmtell 04.10.2008 03:03

это очень просто, код предполагает, что это не что иное, как обернутое исключение. но я надеялся, что люди попытаются выйти за рамки очевидного. подобные тирады можно сказать и в старом assert (), но он служит очень полезной задаче. этот трюк просто пытается устранить недостаток старого assert () без дополнительных затрат.

wilhelmtell 04.10.2008 03:09

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

wilhelmtell 04.10.2008 03:11

Правильно ли я считаю, что это не может работать с проверкой некопируемых интеллектуальных указателей? Например: std::unique_ptr<int> p; Assert( p ); Разве этот код не должен приводить к сбою компиляции (из-за копии), что означает, что единственный способ его использовать - это выполнить p.get (), а затем потерять большую часть универсальности?

Klaim 31.07.2012 01:58

Теперь, когда я думаю об этом, это можно решить, установив по умолчанию A на bool. Что вы думаете?

Klaim 31.07.2012 02:01

Поскольку это доказательство концепции работает, вы можете просто использовать макрос с поддержкой шаблонов, чтобы отразить точное использование и вывод assert(), ссылаясь на утверждать и Замена текстовых макросов. Самая сложная часть (если вы используете GCC) будет получить имя файла без суффикса. К счастью, есть фрагмент кода от @RichardHodges, в котором вы можете почерпнуть вдохновение из здесь.

Fake Code Monkey Rashid 28.08.2020 19:40

Утверждения в C / C++ выполняются только в отладочных сборках. Так что во время выполнения этого не произойдет. Как правило, утверждения должны отмечать вещи, которые, если они происходят, указывают на ошибку и обычно показывают предположения в вашем коде и т. д.

Если вы хотите иметь код, который проверяет наличие ошибок во время выполнения (в выпуске), вам, вероятно, следует использовать исключения, а не утверждения, поскольку они предназначены для этого. Ваш ответ в основном обертывает генератор исключения в синтаксисе assert. Хотя это будет работать, в этом нет особого преимущества, которое я могу видеть по сравнению с простым выбросом исключения в первую очередь.

Утверждения, работающие или не выполняющиеся в производственных сборках, являются опцией компилятора. Они определенно запускаются в gcc по умолчанию, независимо от параметров оптимизации.

Dark Shikari 04.10.2008 00:05

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

Alexandre C. 23.06.2010 20:19

Особенно, если ваша программа приведет к неопределенному поведению, если это утверждение станет ложным; это позволит клиенту, по крайней мере, дать более ценное сообщение об ошибке и упростить шаги воспроизведения; учитывая, что это НИКОГДА не должно было случиться; и вы думали (и гарантировали), что это не так.

deceleratedcaviar 03.06.2011 09:49

@DarkShikari Формулировка здесь, к сожалению, расплывчата. assert() становится нерабочим только в том случае, если определен NDEBUG - и я не думаю, что какой-либо компилятор автоматически определяет это на основе заданных параметров оптимизации и / или отладки. Может быть, IDE делают, и это сбивает с толку. Как бы то ни было, спор тогда сводится к тому, определять ли NDEBUG в выпущенной сборке, конфликтует ли это с каким-либо другим кодом #ifdefd, нужно ли нам писать собственный макрос для получения более точного поведения и т. д.

underscore_d 27.07.2017 19:05

функции отчетов об ошибках в glib использует подход продолжения после утверждения. glib - это базовая библиотека независимости от платформы, которую использует Gnome (через GTK). Вот макрос, который проверяет предварительное условие и печатает трассировку стека, если предварительное условие не выполняется.

#define RETURN_IF_FAIL(expr)      do {                  \
 if (!(expr))                                           \
 {                                                      \
         fprintf(stderr,                                \
                "file %s: line %d (%s): precondition `%s' failed.", \
                __FILE__,                                           \
                __LINE__,                                           \
                __PRETTY_FUNCTION__,                                \
                #expr);                                             \
         print_stack_trace(2);                                      \
         return;                                                    \
 };               } while(0)
#define RETURN_VAL_IF_FAIL(expr, val)  do {                         \
 if (!(expr))                                                       \
 {                                                                  \
        fprintf(stderr,                                             \
                "file %s: line %d (%s): precondition `%s' failed.",     \
                __FILE__,                                               \
                __LINE__,                                               \
                __PRETTY_FUNCTION__,                                    \
                #expr);                                                 \
         print_stack_trace(2);                                          \
         return val;                                                    \
 };               } while(0)

Вот функция, которая печатает трассировку стека, написанную для среды, использующей набор инструментов gnu (gcc):

void print_stack_trace(int fd)
{
    void *array[256];
    size_t size;

    size = backtrace (array, 256);
    backtrace_symbols_fd(array, size, fd);
}

Вот как вы бы использовали макросы:

char *doSomething(char *ptr)
{
    RETURN_VAL_IF_FAIL(ptr != NULL, NULL);  // same as assert(ptr != NULL), but returns NULL if it fails.

    if ( ptr != NULL )        // Necessary if you want to define the macro only for debug builds
    {
       ...
    }

    return ptr;
}

void doSomethingElse(char *ptr)
{
    RETURN_IF_FAIL(ptr != NULL);
}

@wilhelmtell Я считаю, что это лучшее решение, потому что я не согласен с использованием исключения. Исключение должно быть для чего-то особенного, однако обработка Assert - нет. Кроме того, если вы планируете оставить Asserts включенными в сборке релиза, что, я думаю, вам следует делать, если производительность не является жизненно важной, этот метод будет более эффективным. Вы также можете настроить его так, чтобы ваша программа не выходила из строя, когда это произойдет, и вы получите информацию обратно. Предлагаю прочитать главу книги «Прагматичный программист» об утверждениях и исключениях. Плюс эта ссылка.

ForceMagic 08.03.2012 10:22

Вот что у меня в assert.h (Mac OS 10.4):

#define assert(e) ((void) ((e) ? 0 : __assert (#e, __FILE__, __LINE__)))
#define __assert(e, file, line) ((void)printf ("%s:%u: failed assertion `%s'\n", file, line, e), abort(), 0)

Исходя из этого, замените вызов abort () на throw (исключение). И вместо printf вы можете отформатировать строку в сообщение об ошибке исключения. В итоге получится что-то вроде этого:

#define assert(e) ((void) ((e) ? 0 : my_assert (#e, __FILE__, __LINE__)))
#define my_assert( e, file, line ) ( throw std::runtime_error(\
   std::string(file:)+boost::lexical_cast<std::string>(line)+": failed assertion "+e))

Я не пробовал скомпилировать, но смысл вы поняли.

Примечание: вам необходимо убедиться, что заголовок «исключение» всегда включен, а также заголовок boost (если вы решите использовать его для форматирования сообщения об ошибке). Но вы также можете сделать my_assert функцией и объявить только ее прототип. Что-то вроде:

void my_assert( const char* e, const char* file, int line);

И реализуйте его где-нибудь, где вы можете свободно включать все нужные заголовки.

Оберните его в какой-нибудь #ifdef DEBUG, если он вам нужен, или нет, если вы всегда хотите запускать эти проверки.

Так что остается спорить, лучше ли макрос или шаблонная функция. Кроме того, оператор последовательности означает, что операнды могут выполняться в любом произвольном порядке. Я сомневаюсь, что это то, что вы имеете в виду в __asert ().

wilhelmtell 07.11.2008 04:50

Функция «__assert» в первом блоке исходит из моего системного заголовка (0S 10.4). Надеюсь, они знают, что делают. Макрос упрощает получение информации о ФАЙЛАХ и СТРОКАХ. Но в какой-то момент становится довольно косметическим ...

rlerallut 08.11.2008 03:08

_set_error_mode(_OUT_TO_MSGBOX);

поверьте, эта функция может вам помочь.

Похоже, это функция только для Windows: связь

Josh Sanford 26.01.2017 19:10

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

underscore_d 27.07.2017 19:06

Если вы хотите бросить символьную строку с информацией об утверждении: http://xll8.codeplex.com/SourceControl/latest#xll/ensure.h

Другие вопросы по теме