Предотвращение предупреждений о неиспользуемых переменных при использовании assert () в сборке выпуска - PullRequest
53 голосов
/ 22 апреля 2009

Иногда локальная переменная используется с единственной целью проверки ее в assert (), например, -

int Result = Func();
assert( Result == 1 );

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

Возможный обходной путь -

int Result = Func();
if ( Result == 1 )
{
    assert( 0 );
}

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

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

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

Ответы [ 16 ]

50 голосов
/ 22 апреля 2009

Мы используем макрос, чтобы специально указать, когда что-то не используется:

#define _unused(x) ((void)(x))

Тогда в вашем примере вы получите:

int Result = Func();
assert( Result == 1 );
_unused( Result ); // make production build happy

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

24 голосов
/ 12 июня 2009

Я бы не смог дать лучший ответ, чем этот, который решает эту проблему, и многие другие:

Глупые хитрости C ++: приключения в assert

#ifdef NDEBUG
#define ASSERT(x) do { (void)sizeof(x);} while (0)
#else
#include <assert.h>
#define ASSERT(x) assert(x)
#endif
9 голосов
/ 22 апреля 2009

Вы можете создать другой макрос, который позволит вам избежать использования временной переменной:

#ifndef NDEBUG
#define Verify(x) assert(x)
#else
#define Verify(x) ((void)(x))
#endif

// asserts that Func()==1 in debug mode, or calls Func() and ignores return
// value in release mode (any braindead compiler can optimize away the comparison
// whose result isn't used, and the cast to void suppresses the warning)
Verify(Func() == 1);
8 голосов
/ 22 апреля 2009
int Result = Func();
assert( Result == 1 );

Эта ситуация означает, что в режиме релиза вы действительно хотите:

Func();

Но Func не является недействительным, то есть он возвращает результат, то есть это запрос .

Предположительно, помимо возврата результата, Func изменяет что-то (иначе зачем его вызывать, а не использовать его результат?), Т. Е. Это команда .

По принципу разделения команд и запросов (1), Func не должно быть командой и запросом одновременно. Другими словами, запросы не должны иметь побочных эффектов, а «результат» команд должен быть представлен доступными запросами о состоянии объекта.

Cloth c;
c.Wash(); // Wash is void
assert(c.IsClean());

лучше

Cloth c;
bool is_clean = c.Wash(); // Wash returns a bool
assert(is_clean);

Первый не дает вам никаких предупреждений подобного рода, второй делает.

Итак, вкратце, мой ответ: не пишите такой код:)

Обновление (1): Вы запросили ссылки на Принцип разделения команд и запросов . Википедия довольно информативна. Я читал об этой методике проектирования в Создание объектно-ориентированного программного обеспечения, 2-е редактирование Бертраном Мейером.

Обновление (2): j_random_hacker комментирует "OTOH, каждая функция" command "f (), которая ранее возвращала значение, теперь должна установить некоторую переменную last_call_to_f_succeeded или подобную". Это справедливо только для функций, которые ничего не обещают в своем контракте, то есть функций, которые могут «преуспеть» или нет, или аналогичной концепции. При Проектирование по контракту соответствующее число функций будет иметь постусловий , поэтому после «Empty ()» объект будет «IsEmpty ()», а после «Encode ()» строка сообщения будет IsEncoded (), без необходимости проверять. Таким же образом и несколько симметрично, вы не вызываете специальную функцию «IsXFeasible ()» перед каждым вызовом процедуры «X ()»; потому что, как правило, по замыслу вы знаете, что выполняете предварительные условия X в момент вызова.

3 голосов
/ 20 декабря 2018

С недавним C ++ я бы просто сказал:

[[maybe_unused]] int Result = Func();
assert( Result == 1 );

Подробнее об этом атрибуте см. https://en.cppreference.com/w/cpp/language/attributes/maybe_unused.

По сравнению с трюком (void)Result мне он больше нравится, потому что вы напрямую декорируете объявление переменной, а не просто добавляете что-то в качестве запоздалой мысли.

3 голосов
/ 22 апреля 2009

Вы можете использовать:

Check( Func() == 1 );

И реализуйте свою функцию Check (bool) как хотите. Он может либо использовать assert, либо выдать конкретное исключение, записать в файл журнала или в консоль, иметь разные реализации в отладке и выпуске, либо в виде комбинации всех.

2 голосов
/ 10 октября 2018

С C ++ 17 мы можем сделать:

[[maybe_unused]] int Result = Func();

хотя это требует немного дополнительной типизации по сравнению с подстановкой assert. См. этот ответ .

Примечание: добавлено это, потому что это первый гугл хит "c ++ assert unused variable".

2 голосов
/ 04 февраля 2017

Самое простое - объявить / назначить эти переменные, только если утверждения будут существовать. Макрос NDEBUG специально определен, если утверждения не будут выполнены (сделано таким образом только потому, что, я думаю, -DNDEBUG - удобный способ отключить отладку), так что эта измененная копия @ Jardel's ответ должен работать (см. комментарий @AdamPeterson к этому ответу):

#ifndef NDEBUG
int Result =
#endif
Func();
assert(Result == 1);

или, если это не соответствует вашим вкусам, возможны всевозможные варианты, например, это:

#ifndef NDEBUG
int Result = Func();
assert(Result == 1);
#else
Func();
#endif

В целом, при использовании этого материала, будьте осторожны, чтобы никогда не было возможности для построения разных модулей перевода с разными NDEBUG макро-состояниями - особенно re. утверждения или другое условное содержимое в общедоступных заголовочных файлах. Опасность заключается в том, что вы или пользователи вашей библиотеки можете случайно создать иную версию определения встроенной функции, отличную от той, которая используется внутри скомпилированной части библиотеки, тихо нарушив правило one Definition и настроив поведение во время выполнения. не определено.

2 голосов
/ 22 апреля 2009

Это плохое использование assert, ИМХО. Утверждение не предназначено как инструмент сообщения об ошибках, оно предназначено для утверждения предварительных условий. Если Результат не используется в другом месте, это не является предварительным условием.

1 голос
/ 23 апреля 2016

В большинстве ответов предлагается использовать static_cast<void>(expression) трюк в Release сборках для подавления предупреждения, но это на самом деле неоптимально, если вы собираетесь делать проверки действительно Debug только. Цели рассматриваемого макроса утверждения:

  1. Выполнять проверки в режиме Debug
  2. Ничего не делать в режиме Release
  3. Не выдавать предупреждения во всех случаях

Проблема заключается в том, что подход с использованием пустых ролей не достигает второй цели. Хотя предупреждения отсутствуют, выражение, которое вы передали в макрос утверждения, все равно будет оценено . Если вы, например, просто делаете проверку переменных, это, вероятно, не имеет большого значения. Но что, если вы вызовете некоторую функцию в проверке утверждений, например ASSERT(fetchSomeData() == data); (что очень часто встречается в моем опыте)? Функция fetchSomeData() будет по-прежнему вызываться. Это может быть быстро и просто или нет.

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

void myAssertion(bool checkSuccessful)
{
   if (!checkSuccessful)
    {
      // debug break, log or what not
    }
}

#define DONT_EVALUATE(expression)                                    \
   {                                                                 \
      true ? static_cast<void>(0) : static_cast<void>((expression)); \
   }

#ifdef DEBUG
#  define ASSERT(expression) myAssertion((expression))
#else
#  define ASSERT(expression) DONT_EVALUATE((expression))
#endif // DEBUG

int main()
{
  int a = 0;
  ASSERT(a == 1);
  ASSERT(performAHeavyVerification());

  return 0;
}

Вся магия в макросе DONT_EVALUATE. Очевидно, что по крайней мере логически оценка вашего выражения никогда не требуется внутри него. Чтобы усилить это, стандарт C ++ гарантирует, что будет оцениваться только одна из ветвей условного оператора. Вот цитата:

5.16 Условный оператор [expr.cond]

логическое или-выражение? выражение: присваивание-выражение

Группа условных выражений справа налево. Первое выражение контекстно преобразуется в bool. Это оценивается, и если это правда, Результатом условного выражения является значение второго выражение, в противном случае это выражение третьего выражения. Только один из них выражения оценивается.

Я тестировал этот подход в GCC 4.9.0, clang 3.8.0, VS2013 Update 4, VS2015 Update 4 с наиболее жесткими уровнями предупреждений. Во всех случаях нет предупреждений, и проверочное выражение никогда не оценивается в Release сборке (фактически все полностью оптимизируется). Имейте в виду, однако, что при таком подходе вы очень быстро столкнетесь с трудностями, если поместите выражения, которые имеют побочные эффекты, в макрос утверждения, хотя это очень плохая практика.

Кроме того, я ожидаю, что статические анализаторы могут предупреждать о «результате выражения всегда постоянны» (или что-то в этом роде) при таком подходе. Я проверил это с помощью инструментов статического анализа clang, VS2013, VS2015 и не получил подобных предупреждений.

...