Подтвердить что ты не робот

Избегание предупреждений неиспользуемых переменных при использовании assert() в сборке Release

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

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

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

Возможное обходное решение -

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

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

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

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

4b9b3361

Ответ 1

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

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

Тогда в вашем примере у вас будет:

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

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

Ответ 3

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

#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);

Ответ 4

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

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

Func();

Но Func является void, т.е. Возвращает результат, т.е. Это запрос.

Предположительно, помимо возвращения результата, 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): Вы запросили ссылки на принцип разделения команд и запросов. Википедия довольно информативна. Я читал об этой технике проектирования в Object Oriented Software Construction, 2nd Editon by Bertrand Meyer.

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

Ответ 5

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

Check( Func() == 1 );

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

Ответ 6

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

Ответ 7

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

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

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

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

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

Ответ 8

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

Плюс, в любом случае имеет смысл быть внутри функции, потому что он создает автономную единицу, которая имеет свои СОБСТВЕННЫЕ предварительные и пост-условия.

Скорее всего, если функция вернет значение, вы все равно должны будете выполнять некоторую проверку ошибок в режиме выпуска по этому возвращаемому значению. Таким образом, это не должно быть переменной unreferenced.

Изменить, но в этом случае пост-состояние должно быть X (см. комментарии):

Я категорически не согласен с этой точкой, нужно иметь возможность определить состояние сообщения из входных параметров и если это функция-член, любое состояние объекта. Если глобальная переменная изменяет выход функции, функция должна быть реструктурирована.

Ответ 9

Конечно, вы используете макрос для контроля определения assert, например, "_ASSERT". Итак, вы можете сделать это:

#ifdef _ASSERT 
int Result =
#endif /*_ASSERT */
Func();
assert(Result == 1);

Ответ 10

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

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

Проблема заключается в том, что метод void-cast не достигает второй цели. Хотя предупреждения нет, выражение, которое вы передали макросу утверждения, будет по-прежнему оценено. Если вы, например, просто выполняете проверку переменных, это, вероятно, не имеет большого значения. Но что, если вы вызываете какую-то функцию в своей проверке утверждения, например, 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. Очевидно, что по крайней мере логически оценка вашего выражения никогда не нужна внутри него. Чтобы укрепить это, стандарт С++ гарантирует, что будет оценена только одна из ветвей условного оператора. Вот цитата:

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

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

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

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

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

Ответ 11

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

Это заставит компилятор перестать жаловаться на результат, который не используется.

Но вы должны подумать об использовании версии assert, которая делает что-то полезное во время выполнения, например описательные ошибки журнала, в файл, который можно извлечь из рабочей среды.

Ответ 12

Я бы использовал следующее:

#ifdef _DEBUG
#define ASSERT(FUNC, CHECK) assert(FUNC == CHECK)
#else
#define ASSERT(FUNC, CHECK)
#endif

...

ASSERT(Func(), 1);

Таким образом, для сборки релиза компилятору даже не нужно создавать код для утверждения.

Ответ 13

Если этот код находится внутри функции, тогда действуйте и возвращайте результат:

bool bigPicture() {

   //Check the results
   bool success = 1 != Func();
   assert(success == NO, "Bad times");

   //Success is given, so...
   actOnIt();

   //and
   return success;
}

Ответ 14

// Value is always computed.  We also call assert(value) if assertions are
// enabled.  Value is discarded either way.  You do not get a warning either
// way.  This is useful when (a) a function has a side effect (b) the function
// returns true on success, and (c) failure seems unlikely, but we still want
// to check sometimes.
template < class T >
void assertTrue(T const &value)
{
  assert(value);
}

template < class T >
void assertFalse(T const &value)
{ 
  assert(!value);
}

Ответ 15

С помощью c++ 17 мы можем сделать:

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

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

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

Ответ 16

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

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

См. Https://en.cppreference.com/w/cpp/language/attributes/maybe_unused для получения дополнительной информации об этом атрибуте.

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