16

C ++でクラスの不変条件をチェックするための確立されたパターンはありますか?

理想的には、不変条件は、各パブリックメンバー関数の最初と最後に自動的にチェックされます。私の知る限り、クラス付きのCは特別なメンバー関数を提供してbeforeafterましたが、残念ながら、契約による設計は当時あまり人気がなく、Bjarne以外はその機能を使用していなかったため、彼はそれを削除しました。

もちろん、check_invariants()各パブリックメンバー関数の最初と最後に手動で呼び出しを挿入するのは面倒でエラーが発生しやすくなります。RAIIは例外を処理するための最適な武器であるため、不変チェッカーを最初のローカル変数として定義する次のスキームを考え出しました。不変チェッカーは、構築時と破棄時の両方で不変条件をチェックします。

template <typename T>
class invariants_checker
{
    const T* p;

public:

    invariants_checker(const T* p) : p(p)
    {
        p->check_invariants();
    }

    ~invariants_checker()
    {
        p->check_invariants();
    }
};

void Foo::bar()
{
    // class invariants checked by construction of _
    invariants_checker<Foo> _(this);

    // ... mutate the object

    // class invariants checked by destruction of _
}

質問#0:名前のないローカル変数を宣言する方法はないと思いますか?:)

コンストラクタの最後とデストラクタの最初check_invariants()で手動で呼び出す必要があります。ただし、多くのコンストラクタボディとデストラクタボディは空です。その場合、最後のメンバーとしてを使用できますか?FooFooinvariants_checker

#include <string>
#include <stdexcept>

class Foo
{
    std::string str;
    std::string::size_type cached_length;
    invariants_checker<Foo> _;

public:

    Foo(const std::string& str)
    : str(str), cached_length(str.length()), _(this) {}

    void check_invariants() const
    {
        if (str.length() != cached_length)
            throw std::logic_error("wrong cached length");
    }

    // ...
};

質問1:オブジェクトがまだ作成中であっても、そのポインターを介してすぐに呼び出すコンストラクターに渡すthisことは有効ですか?invariants_checkercheck_invariantsFoo

質問2:このアプローチで他に問題がありますか?改善できますか?

質問3:このアプローチは新しいものですか、それともよく知られていますか?より良い解決策はありますか?

4

6 に答える 6

12

回答#0:名前のないローカル変数を使用することはできますが、オブジェクトの存続期間の制御を放棄します。オブジェクトの全体的なポイントは、スコープ外になったときに良いアイデアがあるためです。使用できます

void Foo::bar()
{
    invariants_checker<Foo>(this); // goes out of scope at the semicolon
    new invariants_checker<Foo>(this); // the constructed object is never destructed
    // ...
}

しかし、どちらもあなたが望むものではありません。

回答#1:いいえ、それは有効ではないと思います。によって参照されるオブジェクトthisは、コンストラクターが終了したときにのみ完全に構築されます(したがって、存在し始めます)。ここで危険なゲームをプレイしています。

回答#2&#3:このアプローチは新しいものではありません。たとえば、「不変条件C ++テンプレートをチェックする」などの単純なグーグルクエリは、このトピックで多くのヒットをもたらします。->特に、次のように演算子の過負荷を気にしない場合は、このソリューションをさらに改善できます。

template <typename T>
class invariants_checker {
public:
  class ProxyObject {
  public:
    ProxyObject(T* x) : m(x) { m->check_invariants(); }
    ~ProxyObject() { m->check_invariants(); }
    T* operator->() { return m; }
    const T* operator->() const { return m; }
  private:
    T* m;
  };

invariants_checker(T* x) : m(x) { }

ProxyObject operator->() { return m; } 
const ProxyObject operator->() const { return m; }

private:
   T* m;
};

メンバー関数呼び出しの期間中、コンストラクタとデストラクタでチェックを実行する匿名プロキシオブジェクトを作成するという考え方です。上記のテンプレートは次のように使用できます。

void f() {
  Foo f;
  invariants_checker<Foo> g( &f );
  g->bar(); // this constructs and destructs the ProxyObject, which does the checking
}
于 2011-01-19T14:16:47.927 に答える
2

理想的には、不変条件は、各パブリックメンバー関数の最初と最後に自動的にチェックされます。

これはやり過ぎだと思います。代わりに、不変条件を慎重にチェックします。クラスのデータメンバーはprivate(右?)なので、そのメンバー関数のみがデータメンバーを変更できるため、不変条件を無効にできます。したがって、不変条件に参加するデータメンバーに変更を加えた直後に、不変条件をチェックする必要がなくなります。

于 2011-01-19T14:27:09.793 に答える
1

質問#0:名前のないローカル変数を宣言する方法はないと思いますか?:)

通常、マクロとを使用して何かを作成できます__LINE__が、同じスコープ内に(直接)複数あるべきではないため、奇妙な名前を選択した場合は、すでに実行されているはずです。これ

class invariants_checker {};

template<class T>
class invariants_checker_impl : public invariants_checker {
public:
    invariants_checker_impl(T* that) : that_(that) {that_->check_invariants();}
    ~invariants_checker_impl()                     {that_->check_invariants();}
private:
    T* that_;
};

template<class T>
inline invariants_checker_impl<T> get_invariant_checker(T* that)
{return invariants_checker_impl<T>(that);}

#define CHECK_INVARIANTS const invariants_checker& 
   my_fancy_invariants_checker_object_ = get_invariant_checker(this)

私のために働きます。

質問1:オブジェクトがまだ作成中であっても、そのポインターを介してすぐに呼び出すコンストラクターに渡すthisことは有効ですか?invariants_checkercheck_invariantsFoo

それがUBテクニカルを呼び出すかどうかはわかりません。実際には、そうすることは確かに安全です-実際には、他のクラスメンバーに対して特定の位置で宣言されなければならないクラスメンバーが遅かれ早かれ問題になるという事実がない場合。

質問2:このアプローチで他に問題がありますか?改善できますか?

#2を参照してください。適度なサイズのクラスを受講し、20人の開発者による5年間の拡張とバグ修正を追加します。これを、少なくとも1回は約98%で台無しにする可能性があると思います。
データメンバーに叫びのコメントを追加することで、これをいくらか軽減できます。まだ。

質問3:このアプローチは新しいものですか、それともよく知られていますか?より良い解決策はありますか?

私はこのアプローチを見たことがありませんでしたが、あなたの説明を与えられてbefore()after()私はすぐに同じ解決策を考えました。

Stroustrupには、何年も前(〜15?)にoperator->()、プロキシを返すためのハンドルクラスのオーバーロードについて説明した記事があったと思います。これにより、ctorおよびdtorで、呼び出されるメソッドを認識せずに、アクションの前後に実行できます。

編集: Frerichがこれを具体化する答えを追加したことがわかります。もちろん、クラスをそのようなハンドルで使用する必要がない限り、これはクラスのユーザーの負担になります。(IOW:動作しません。)

于 2011-01-19T14:03:45.493 に答える
1

#0:いいえ。ただし、マクロを使用すると状況が少し良くなる可能性があります(問題がなければ)

#1:いいえ、しかしそれは異なります。これが本体の前で逆参照される原因となるようなことは何もできません(これはあなたの場合ですが、直前なので、機能する可能性があります)。つまり、これを保存することはできますが、フィールドや仮想関数にアクセスすることはできません。それが仮想である場合、check_invariants()を呼び出すことはOKではありません。ほとんどの実装で機能すると思いますが、機能することが保証されているわけではありません。

#2:退屈で、価値がないと思います。これは、不変チェックに関する私の経験です。私は単体テストが好きです。

#3:私はそれを見ました。あなたがそれをするつもりなら、それは私にとって正しい方法のように思えます。

于 2011-01-19T14:32:40.460 に答える
0

デストラクタが頻繁にスローする関数を呼び出しているという問題がはっきりとわかります。これはC++ではノーノーですよね。

于 2011-01-19T14:59:40.930 に答える
0

ユニットテストは、より良いパフォーマンスでより小さなコードにつながるより良い代替手段です

于 2011-01-19T14:01:22.740 に答える