Отвечать на комментарий большим — плохая идея, поэтому вот мой подробный ответ по следующей теме:
Хотя это возможно, было бы сложно включить это определение во все файлы тестовых сценариев. Также это не ограничивается только BOOST_REQUIRE, но также применимо к assert, SDL_Assert или любому другому пользовательскому макросу, который может использовать пользователь.
Следует понимать, что существует три типа тестовых макросов и о каждом стоит поговорить отдельно.
Макросы первого типа просто предупреждают о том, что в отладочной версии что-то пошло не так. Типичный пример — макрос assert
. Следующий код заставит анализатор PVS-Studio выдать предупреждение:
T* p = dynamic_cast<T *>(x);
assert(p);
p->foo();
Анализатор укажет на возможное разыменование нулевого указателя и будет прав. Проверки, использующей assert
, недостаточно, поскольку она будет удалена из версии Release. То есть получается, что чека нет. Лучший способ реализовать это — переписать код примерно так:
T* p = dynamic_cast<T *>(x);
if (p == nullptr)
{
assert(false);
throw Error;
}
p->foo();
Этот код не вызовет предупреждение.
Вы можете возразить, что на 100 % уверены, что dynamic_cast
никогда не вернет nullptr
. Я не принимаю этот аргумент. Если вы полностью уверены, что приведение ВСЕГДА правильное, вам следует использовать более быстрый static_cast
. Если вы не уверены, вы должны проверить указатель, прежде чем разыменовывать его.
Ну, хорошо, я понимаю твою точку зрения. Вы уверены, что с кодом все в порядке, но на всякий случай нужно проверить с помощью dynamic_cast. ОК, тогда используйте следующий код:
assert(dynamic_cast<T *>(x) != nullptr);
T* p = static_cast<T *>(x);
p->foo();
Мне это не нравится, но по крайней мере так быстрее, так как более медленный оператор dynamic_cast в релизной версии будет опущен, а анализатор промолчит.
Переходим к следующему типу макросов.
Макросы второго типа просто предупреждают о том, что в отладочной версии что-то пошло не так и используются в тестах. Что отличает их от предыдущего типа, так это то, что они останавливают тестируемый алгоритм, если условие ложно, и генерируют сообщение об ошибке.
Основная проблема с этими макросами заключается в том, что функции не помечены как невозвратные. Вот пример.
Предположим, у нас есть функция, которая генерирует сообщение об ошибке, вызывая исключение. Вот как выглядит его объявление:
void Error(const char *message);
А вот как объявляется тестовый макрос:
#define ENSURE(x) do { if (!x) Error("zzzz"); } while (0)
Используя указатель:
T* p = dynamic_cast<T *>(x);
ENSURE(p);
p->foo();
Анализатор выдаст предупреждение о возможном разыменовании нулевого указателя, но на самом деле код безопасен. Если указатель нулевой, функция Error
вызовет исключение и, таким образом, предотвратит разыменование указателя.
Нам просто нужно сообщить об этом анализатору с помощью одного из средств аннотации функции, например:
[[noreturn]] void Error(const char *message);
or:
__declspec(noreturn) void Error(const char *message);
Это поможет исключить ложное предупреждение. Итак, как видите, в большинстве случаев довольно легко исправить ситуацию, используя собственные макросы.
Однако это может быть сложнее, если вы имеете дело с небрежно реализованными макросами из сторонних библиотек.
Это приводит нас к третьему типу макросов. Вы не можете их изменить, и анализатор не может понять, как именно они работают. Это обычная ситуация, так как макросы могут быть реализованы весьма экзотическими способами.
В этом случае у вас остается три варианта:
- подавить предупреждение с помощью одного из способов подавления ложных срабатываний, описанных в документации;
- используйте технику, описанную в предыдущем ответе;
- Свяжитесь с нами по электронной почте.
Мы постепенно добавляем поддержку различных хитрых макросов из популярных библиотек. На самом деле анализатор уже знаком с большинством конкретных макросов, с которыми вы можете столкнуться, но фантазия программистов неиссякаема, и мы просто не можем предусмотреть каждую возможную реализацию.
person
AndreyKarpov
schedule
16.11.2017