13

C++のクラス不変式を調べるための確立されたパターンはありますか?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:名前のないローカル変数を宣言する方法がないと思いますか? :)

Fooコンストラクタの最後とFooデストラクタの先頭に手動でcheck_invariants()を呼び出す必要があります。しかし、多くのコンストラクタ本体とデストラクタ本体は空です。その場合、最後のメンバーとしてinvariants_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:それはすぐにFooオブジェクトがまだ建設中であっても、そのポインタ経由check_invariantsを呼び出しinvariants_checkerコンストラクタにthisを渡すために有効ですか?

質問#2:この方法に他の問題はありますか?あなたはそれを改善できますか?

質問#3:このアプローチは新しいかよく知られていますか?より良いソリューションがありますか?

+0

イニシャライザのリストで 'this'を使用することはできません。あなたはコンストラクタの本体でそれを使うことができます。 – Benoit

+0

@Benoit:*とはどういう意味ですか?それは厳格に禁止されていますか?それは未定義の振る舞いを引き起こしますか? – fredoverflow

+1

Thorsten Ottesen(私はそうだったと思う)はDesign by Contractの提案をしていた。最初のラウンドでは、内部のものと外部のものを決めるのが難しいため(彼らは内部呼び出しのために不変のものを一時的に破ることができます)、地面から降りませんでした。しかしそれはまだ現れるかもしれません。それが積極的に働いているかどうかはわかりません。 –

答えて

10

Answer#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:このアプローチは新しいものではなく、たとえば次のような単純なGoogleクエリです。 "check invariants C++ template"はこのトピックについて多くのヒットをもたらします。特に、この解決策は、あなたがこのように、->演算子をオーバーロードする気にならないのであればさらに向上させることができます。

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 
} 
+1

+1を使って 'ProxyObject'の考え方を導入したいと考えています。非常に素晴らしい! – Nawaz

+1

0)は名前のない変数ではなく、一時的なオブジェクトです。宣言された変数はありませんが、一時的なものを作成し、何もしません。最後のブロック+1と 'operator->'のオーバーロードの示唆があります。ロックされたスマートポインタを標準に追加する提案は、ほとんどの場合、ロックの精度が最良の選択ではないという理由で拒否されました) –

+1

@David:名前のない変数(匿名の値)と一時オブジェクトの違いは何ですか? –

0

ユニットテストは、より優れた性能を持つ小さなコードにつながり、より良い代替手段です

+0

Dは契約書*と*単位テストの両方をその言語に組み込んでいますか? :) – fredoverflow

+0

@Fred:ええ、あなたが正しい、時々不変量をチェックするのは良い考えですが、私はむしろユニットテスト –

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:それはすぐにFooオブジェクトがまだ建設中であっても、そのポインタ経由check_invariantsを呼び出しinvariants_checkerコンストラクタにthisを渡すために有効ですか?

私はそれがUB技術を呼び出すかどうかはわかりません。実際には、実際にそうすることは確か安全でしょう - 実際には、他の階級のメンバーに関して特定の位置に宣言されなければならない階級のメンバーは遅かれ早かれ問題になるでしょう。

質問#2:このアプローチで他の問題はありますか?あなたはそれを改善できますか?

#2を参照してください。適度なサイズのクラスを用意し、20年以上の開発者による拡張とバグ修正の半分を追加して、これを少なくとも約98%で1回以上混乱させるチャンスを考えます。
あなたはややデータメンバに叫ぶのコメントを追加することでこれを緩和することができます。まだ。

質問#3:このアプローチは、新規または既知のですか?より良いソリューションがありますか?

私は、このアプローチを見て、私はすぐに同じソリューションを考えbefore()after()のあなたの説明を与えられていませんでした。

私はStroustrup氏は数年前、彼はプロキシを返すようにoperator->()をオーバーロードハンドルクラスを説明したところ、多くの記事(〜15?)を持っていたと思います。これは、その司令官と司令官において、それを通して呼び出される方法に気づかずに、前後の行動を実行することができます。

編集:私はFrerichがan answer fleshing this outを追加したことがわかり。もちろん、あなたのクラスがすでにこのようなハンドルを使用していなければ、これはクラスのユーザーにとって負担になります。 (IOW:動作しません)

2

、不変量は、自動的に最初に確認されるだろうし、各パブリックメンバ関数の最後

で私は、これはやりすぎだと思います。代わりに不変条件を賢明にチェックする。そのメンバ関数は、データmemebersを変更して、不変条件を無効にすることができるだけなので、あなたのクラスのデータメンバは、(右?)privateです。したがって、不変量のデータメンバーに変更を加えた直後に、不変量をチェックすることで、その不変式に参加することができます。

1

#0:(あなたはそれでOKなら)いいえ、しかし、物事はマクロで少し良いかもしれ

#1:いや、それが依存しています。あなたはこれを身体の前で逆参照されるようなことをすることはできません(これはあなたのものですが、直前にはうまくいきます)。つまり、これを保存できますが、フィールドや仮想関数にはアクセスできません。 check_invariants()を呼び出すことは、仮想であればOKではありません。私はそれがほとんどの実装ではうまくいくと思いますが、動作することは保証されていません。

#2:退屈で、価値がないと思います。これは不変の検査での私の経験でした。私はユニットテストを好む。

#3:私はそれを見ました。あなたがそれをやろうとすれば、それは私にとって正しい道のようです。

0

私は明らかに、デストラクタがよく投げる関数を呼び出していることが分かります。これはC++のno-noです。

+0

絶対に - デストラクタは例外時にスタックを巻き戻している間に呼び出されるためです。その違いを知る方法はありません。しかし、私は不変の検査がテストの間にのみ存在し、生産コードではないことを期待しています。だからOKかもしれません。 –

+1

@Lou:不変チェックが発生すると、コードにプログラミングエラーがあり、クラスが決して存在しない状態にあることを意味します。この場合、すべてのベットはオフであり、プログラムは単に終了する必要があります。これがdtorで発見されたかどうかは、まったく問題ではない、とにかくプログラムが終了するとき – sbi