1

たとえば、特定のリアルタイム システムの特定の機能が 20 ミリ秒以下で動作することを保証する必要があります。関数の開始時と終了時の時間を単純に測定し、その差が満足できるものであると断言できます。そして、これを C++ で行います。

しかし、これは契約とほとんど同じように見えますが、時間のチェックは事後条件であり、開始時の時間測定はまったく条件ではありません。それの表記だけでなく、構築上の理由からも契約に入れるとよいでしょう。

では、コントラクト機能を使用して関数の動作時間を確認することはできますか?

4

1 に答える 1

2

ある種、しかしあまりよくありません。その理由は、in{} ブロックで宣言された変数が out{} ブロックに表示されないためです。(これを変更することについて議論されているため、in ブロックにコピーを作成することで pre と post の状態を確認できますが、何も実装されていません。)

したがって、これは機能しません

void foo()
in { auto before = Clock.currTime(); }
out { assert(Clock.currTime - before < dur!"msecs"(20)); }
body { ... }

in からの変数は out に引き継がれず、未定義の識別子エラーが発生します。ただし、潜在的な回避策があるため、「ある程度」と言います。

import std.datetime;
struct Foo {
    SysTime test_before;
    void test()
    in {
        test_before = Clock.currTime();
    }
    out {
        assert(Clock.currTime - test_before < dur!"msecs"(20));
    }
    body {

    }
}

変数を構造体の通常のメンバーとして宣言します。しかし、これは、関数ごとに無用な変数がたくさんあることを意味し、再帰では機能せず、メンバーの名前空間を汚染するだけです。

私の一部は、独自のスタックを脇に置いて、in{} で時間をプッシュし、out{} でそれをポップしてチェックすることができると考えています....しかし、簡単なテストでは、継承が取得されると壊れやすいことが示されています。関与。毎回 in{} ブロックを繰り返すと、うまくいくかもしれません。しかし、これは私にはひどくもろいように思えます。コントラクト継承のルールは、継承ツリーのすべての out{} ブロックを渡す必要がありますが、in{} ブロックのいずれか 1 つだけを渡す必要があります。そのため、チェーンの下流に別の in{} がある場合、時刻をプッシュするのを忘れる可能性があり、out がそれをポップしようとすると、スタックがアンダーフローします。{}

// just for experimenting.....
SysTime[] timeStack; // WARNING: use a real stack here in production, a plain array will waste a *lot* of time reallocating as you push and pop on to it

 class Foo {
    void test()
      in {
        timeStack ~= Clock.currTime();
      }
      out {
         auto start = timeStack[$-1];
         timeStack = timeStack[0 .. $-1];
         assert(Clock.currTime - start < dur!"msecs"(20));
         import std.stdio;
         // making sure the stack length is still sane
         writeln("stack length ", timeStack.length);
       }
    body { }
}

class Bar : Foo {
 override void test()
  in {
     // had to repeat the in block on the child class for this to work at all
    timeStack ~= Clock.currTime();
  }
  body {
    import core.thread;
    Thread.sleep(10.msecs); // bump that up to force a failure, ensuring the test is actually run
  }
}

それは機能しているように見えますが、それは価値があるというよりは面倒だと思います。プログラムが大きくなるにつれて、何らかの形で壊れることが予想されます。テストでプログラムが壊れた場合、それは目的に反します。

明示的なテストでチェックするだけで要件が満たされる場合は、おそらくユニットテストとして実行します{}(ただし、-release スイッチを使用してコンパイルすると、D のほとんどのアサートと同様にコントラクトが削除されることに注意してください。確実に失敗する必要がある場合は、アサートするのではなく、例外をスローします。これは、デバッグ モードとリリース モードで常に機能するためです。)

または、C++ と同様に、関数またはヘルパー構造体などでアサートを使用して実行できます。私はスコープガードを使用します:

void test() {
    auto before = Clock.currTime();
    scope(exit) assert(Clock.currTime - before < dur!"msecs"(20)); // or import std.exception; and use enforce instead of assert if you want it in release builds too
    /* write the rest of your function */
}

もちろん、ここではサブクラスにもコピーする必要がありますが、とにかく in{} ブロックでそれを行う必要があるように思われるので、まあ、少なくとも before 変数はローカルです。

要するに、C++ で行ったのとほぼ同じ方法で行うのがおそらく最善であると思います。

于 2013-10-22T14:31:34.030 に答える