web-dev-qa-db-ja.com

リリースビルドでassert()を使用する際の未使用の変数の警告の回避

ローカル変数は、assert()でチェックするという唯一の目的で使用されることがあります。

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

Releaseビルドでコードをコンパイルするとき、assert()は通常無効になっているため、このコードはResultが設定されているが読み取られないという警告を生成する場合があります。

可能な回避策は-

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

しかし、それはあまりにも多くのタイピングを必要とし、目には簡単ではなく、条件が常にチェックされる原因になります(はい、コンパイラーはチェックを最適化するかもしれませんが、それでも)。

警告を発生させない方法でこのassert()を表現する別の方法を探していますが、それでも簡単に使用でき、assert()のセマンティクスを変更しないようにしています。

(コードのこの領域で#pragmaを使用して警告を無効にすることはオプションではなく、警告レベルを下げて警告を消すこともオプションではありません...)。

54
Hexagon

マクロを使用して、未使用の部分を具体的に示します。

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

次に、あなたの例では、次のようになります:

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

そうすれば、(a)本番ビルドが成功し、(b)変数が未使用であることがコードで明らかです設計上、それが忘れられているだけではありません。これは、関数のパラメーターが使用されていない場合に特に役立ちます。

50
Graeme Perrow

私はこれよりも良い答えを出すことができず、その問題に対処します。

愚かなC++トリック:アサートの冒険

#ifdef NDEBUG
#define ASSERT(x) do { (void)sizeof(x);} while (0)
#else
#include <assert.h>
#define ASSERT(x) assert(x)
#endif
26
hcpizzi

一時変数の使用を回避できる別のマクロを作成できます。

#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);
9
Adam Rosenfield
int Result = Func();
assert( Result == 1 );

この状況は、リリースモードでは、次のことが本当に必要であることを意味します。

Func();

ただし、Funcはvoidではありません。つまり、結果を返します。つまり、queryです。

おそらく、結果を返す以外に、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):コマンドクエリ分離の原則に関する参照を求めました-)。 ウィキペディア はかなり有益です。この設計手法については、Bertrand Meyerによる Object Oriented Software Construction、2nd Editon で読みました。

Update(2):j_random_hackerは「OTOH、すべての「コマンド」関数をコメントしますf()以前に値を返しましたここで、変数last_call_to_f_succeededなどを設定する必要があります。」これは、契約で何も約束しない関数、つまり「成功する」かどうかわからない関数、または同様の概念にのみ当てはまります。with契約による設計、関連する数の関数には事後条件があるため、「Empty()」の後にオブジェクト「IsEmpty()」となり、「Encode()」の後のメッセージ文字列は「IsEncoded()」となり、確認する必要はありません。同様に、対称的に、特別な関数を呼び出す必要はありませんプロシージャ "X()"を呼び出すたびにIsXFeasible() "を実行します。通常、呼び出しの時点でXの前提条件を満たしていることが設計上わかっているためです。

8
Daniel Daranas

最近のC++では、次のように言います。

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

この属性の詳細については、 https://en.cppreference.com/w/cpp/language/attributes/maybe_unused を参照してください。

(void)Resultトリックと比較して、変数宣言を直接装飾するので、後付けとして何かを追加するだけではないので、私はそれが好きです。

4
Gathar

あなたが使用することができます:

Check( Func() == 1 );

そして、必要に応じてCheck(bool)関数を実装します。アサートを使用するか、特定の例外をスローするか、ログファイルまたはコンソールに書き込むか、デバッグとリリースで異なる実装を行うか、またはすべてを組み合わせます。

3
Jem

C++ 17を使用すると、次のことができます。

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

ただし、アサート置換と比較して、少し余分な入力が必要です。 この答え を参照してください。

注:「c ++アサート未使用変数」の最初のgoogleヒットであるため、これを追加しました。

2
Aelian

これはassert、IMHOの悪い使い方です。アサートはエラー報告ツールとしてではなく、前提条件をアサートするためのものです。 Resultが他の場所で使用されていない場合、それは前提条件ではありません。

2
anon

最も簡単なことは、アサートが存在する場合にのみ、これらの変数を宣言/割り当てることです。 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)を使用してさまざまな翻訳単位を作成する可能性がないことに注意してください。パブリックヘッダーファイル内のアサートまたはその他の条件付きコンテンツ。危険なのは、あなたまたはあなたのライブラリのユーザーが、ライブラリのコンパイルされた部分内で使用されているものとは異なるインライン関数の定義を誤ってインスタンス化し、 1つの定義規則 に静かに違反してランタイムを作成する可能性があることです動作は定義されていません。

2
andybuckley

関数内でアサートを戻り値の前に移動する必要があります。戻り値が参照されていないローカル変数ではないことを知っています。

さらに、関数内にある方が理にかなっています。これは、独自の事前条件と事後条件を持つ自己完結型のユニットを作成するためです。

関数が値を返す場合、とにかくリリースモードでこの戻り値に対してなんらかのエラーチェックを行う必要があります。したがって、そもそも参照されていない変数であってはなりません。

編集、ただしこの場合、投稿条件はXである必要があります(コメントを参照):

私はこの点に強く反対します。入力パラメーターと、それがメンバー関数の場合は任意のオブジェクトの状態からpost条件を判別できるはずです。グローバル変数が関数の出力を変更する場合は、関数を再構築する必要があります。

1
Brian R. Bondy

ほとんどの回答は、Releaseビルドでstatic_cast<void>(expression)トリックを使用して警告を抑制することを提案していますが、チェックを本当にDebugのみにする場合、これは実際には最適ではありません。問題のアサーションマクロの目的は次のとおりです。

  1. Debugモードでチェックを実行します
  2. Releaseモードでは何もしません
  3. すべての場合に警告を発しません

問題は、ボイドキャストアプローチが2番目の目標に到達できないことです。警告はありませんが、アサーションマクロに渡した式はevaluatedのままです。たとえば、変数チェックだけを行う場合、それはおそらく大したことではありません。しかし、アサーションチェックで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 大きい。少なくともlogically式の評価がその内部で必要になることは決してありません。これを強化するために、C++標準では、条件演算子のブランチの1つだけが評価されることが保証されています。見積もりは次のとおりです。

5.16条件演算子[expr.cond]

logical-or-expression?式:割り当て式

条件式は右から左にグループ化されます。最初の式は文脈的にブールに変換されます。これは評価され、真の場合、条件式の結果は2番目の式の値になり、それ以外の場合は3番目の式の値になります。これらの式の1つだけが評価されます。

私はこのアプローチをGCC4.9.0、clang 3.8.0、VS2013 Update 4、VS2015 Update 4でテストし、最も厳しい警告レベルを使用しました。すべての場合において、警告はなく、チェック式がReleaseビルドで評価されることはありません(実際、すべてが完全に最適化されています)。ただし、このアプローチでは、副作用のある式をアサーションマクロ内に配置すると、問題がすぐに発生することを覚えておいてください。ただし、これはそもそも非常に悪い習慣です。

また、このアプローチでは、静的アナライザーが「式の結果は常に一定」(またはそのようなもの)について警告する可能性があると思います。私はこれをclang、VS2013、VS2015静的分析ツールでテストしましたが、そのような警告はありませんでした。

1
Sergey Nikitin

確かに、「_ ASSERT」などのマクロを使用してアサート定義を制御します。だから、これを行うことができます:

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

このコードが関数内にある場合は、動作して結果を返します。

bool bigPicture() {

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

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

   //and
   return success;
}
0
Michael
// 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);
}
0

私は以下を使用します:

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

...

ASSERT(Func(), 1);

このように、リリースビルドの場合、コンパイラはアサート用のコードを生成する必要さえありません。

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

これにより、コンパイラーは結果が使用されていないという不満を解消します。

ただし、実稼働環境から取得できるファイルに記述エラーをログに記録するなど、実行時に役立つバージョンのassertの使用を検討する必要があります。

0
John Dibling