Пользовательский макрос assert С++
Я наткнулся на информативную статью: http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/
который указал на большое количество проблем, которые существуют в моем текущем пакете отладочных макросов.
Полный код окончательной версии макроса указан в конце статьи, если вы следуете ссылке.
Общая форма, представленная, выглядит так (кто-то, пожалуйста, поправьте меня, если я ошибаюсь в переносе):
#ifdef DEBUG
#define ASSERT(cond) \
do \
{ \
if (!(cond)) \
{ \
ReportFailure(#cond, __FILE__, __LINE__, 0); \
HALT(); \
} \
} while(0)
#else
#define ASSERT(cond) \
do { (void)sizeof(cond); } while(0)
Задумываясь об изменении моего кода с тем, что я узнал, я заметил пару интересных вариантов, опубликованных в комментариях к этой статье:
Один из них заключался в том, что вы не можете использовать этот макрос с тернарным оператором (т.е. cond?ASSERT(x):func()
), и предложение заключалось в замене if()
тройным оператором и некоторыми скобками, а также оператором запятой. Позже другой комментатор предоставил следующее:
#ifdef DEBUG
#define ASSERT(x) ((void)(!(x) && assert_handler(#x, __FILE__, __LINE__) && (HALT(), 1)))
#else
#define ASSERT(x) ((void)sizeof(x))
#endif
Я думал, что использование логического и &&
является особенно умным в этом случае, и мне кажется, что эта версия более гибкая, чем одна, использующая if
или даже тройную ?:
. Еще лучше, что возвращаемое значение assert_handler
может использоваться для определения того, должна ли программа останавливаться. Хотя я не уверен, почему это (HALT(), 1)
вместо просто HALT()
.
Есть ли какие-то особые недостатки со второй версией здесь, которые я упустил? Это оборачивается do{ } while(0)
, завернутым вокруг макросов, но здесь это не нужно, потому что нам не нужно иметь дело с if
s.
Как вы думаете?
Ответы
Ответ 1
В стандартной библиотеке C и С++ assert
- это макрос, который должен действовать как функция. Часть этого требования заключается в том, что пользователи должны иметь возможность использовать его в выражениях. Например, со стандартным assert
я могу сделать
int sum = (assert(a > 0), a) + (assert(b < 0), b);
который функционально совпадает с
assert(a > 0 && b < 0)
int sum = a + b;
Несмотря на то, что первое может быть не очень хорошим способом написания выражения, трюк по-прежнему очень полезен во многих других случаях.
Это немедленно означает, что если требуется собственный собственный макрос assert
для имитации стандартного поведения и удобства использования assert
, то использование if
или do { } while (0)
в определении assert
не может быть и речи. Один из них ограничивается выражениями в этом случае, что означает использование оператора ?:
или коротких замыкающих логических операторов.
Конечно, если вам не нужно создавать стандартные assert
, то можно использовать что угодно, в том числе if
. Связанная статья, похоже, даже не рассматривает эту проблему, что довольно странно. На мой взгляд, функционально-утвердительный макрос определенно более полезен, чем не-функциональный.
Что касается (HALT(), 1)
... Это делается так, потому что оператор &&
требует действительного аргумента. Возвращаемое значение HALT()
может не представлять допустимый аргумент для &&
. Это может быть void
для того, что я знаю, а это означает, что просто HALT()
просто не будет компилироваться в качестве аргумента &&
. (HALT(), 1)
всегда оценивается как 1
и имеет тип int
, который всегда является допустимым аргументом для &&
. Таким образом, (HALT(), 1)
всегда является допустимым аргументом для &&
, независимо от типа HALT()
.
Ваш последний комментарий о do{ } while(0)
, похоже, не имеет большого смысла. Точка включения макроса в do{ } while(0)
заключается в рассмотрении внешних if
s, а не if
внутри определения макроса. Вам всегда приходится иметь дело с внешними if
s, так как всегда есть шанс, что ваш макрос будет использоваться во внешнем if
. В последнем определении do{ } while(0)
не требуется, потому что этот макрос является выражением. И, будучи выражением, он уже, естественно, не имеет проблем с внешними if
s. Поэтому нет ничего о них. Более того, как я сказал выше, включение его в do{ } while(0)
полностью победит его цель, превратив его в не-выражение.
Ответ 2
Для полноты я опубликовал в С++ версию макроса assert-in 2 files:
#include <pempek_assert.h>
int main()
{
float min = 0.0f;
float max = 1.0f;
float v = 2.0f;
PEMPEK_ASSERT(v > min && v < max,
"invalid value: %f, must be between %f and %f", v, min, max);
return 0;
}
Вам будет предложено:
Assertion 'v > min && v < max' failed (DEBUG)
in file e.cpp, line 8
function: int main()
with message: invalid value: 2.000000, must be between 0.000000 and 1.000000
Press (I)gnore / Ignore (F)orever / Ignore (A)ll / (D)ebug / A(b)ort:
Где
- (I) gnore: игнорировать текущее утверждение
- Игнорировать (F) orever: запомнить файл и строку, в которой было запущено утверждение и
игнорировать его для оставшегося выполнения программы
- Игнорировать (A) ll: игнорировать все остальные утверждения (все файлы и строки)
- (D) ebug: перейдите в отладчик, если он подключен, иначе
abort()
(в Windows,
система предложит пользователю подключить отладчик)
- A (b) ort: вызов
abort()
немедленно
Вы можете узнать об этом больше:
Надеюсь, что это поможет.
Ответ 3
Хотя я не уверен, почему это (HALT(), 1)
вместо просто HALT()
.
Я полагаю, что HALT
может быть макросом (или другим именем в режиме ожидания) для exit
. Скажем, мы хотели использовать exit(1)
для нашей команды HALT
. exit
возвращает void
, который не может быть оценен как второй аргумент &&
. Если вы используете оператор запятой, который оценивает свой первый аргумент, то вычисляет и возвращает значение второго аргумента, мы имеем целое число (1), чтобы вернуться к &&
, хотя мы никогда не достигаем этой точки, потому что HALT()
будет заставит нас остановиться задолго до этого.
В принципе, любая функция, которая заполняется для HALT
, вероятно, будет иметь возвращаемое значение void
, потому что для нее не имеет смысла возвращать какое-либо значение. Мы могли бы заставить его вернуть int
только ради макроса, но если мы уже взламываем макрос, немного больше хакеров не может повредить, не так ли?