0

Doxygen で文書化されている、純粋仮想関数を呼び出す関数をFoo持つこの単純な基本クラスを検討してください。foofoo_

class Foo
{
  public:
    /** \brief This function logs x and does the job */
    void foo(double x);
  protected:
    /** \brief This function does the job */
    virtual void foo_(double x) = 0;
};

void Foo::foo(double x)
{
  std::clog << "'Foo::foo(double x)' is called with x = " << x << std::endl;
  this->foo_(x);
}

この抽象クラスについて文書化する前提条件はありません。

Barここで、正しく機能するための前提条件が存在する派生クラスを考えてみましょう。

class Bar : public Foo
{
  public:
    /**
     * \brief This function does the job
     * \pre   x must be greater or equal to 0
     */
    virtual void foo_(double x);
};

void Bar::foo_(double x)
{
  assert(x >= 0.0 && "PRE: x can't be negative");
  // Do the job
}

foo_ここで、によって呼び出されるを呼び出すときに x に前提条件がありfooます。次に、foo最終的な型に応じて前提条件があります。

いくつかの質問 :

  1. Foo::foo最終的なタイプに関係なく、前提条件を追加する必要がありますか? ユーザーがクラスを使用するときに最終型をまったく知らない場合、それは論理的に見えます。ただし、ユーザーは前提条件なしで別のクラスをBaz派生させ、負の値で明示的に呼び出すこともできます。それは問題ではないはずです。FooBaz::foo(double)
  2. 私のポリモーフィズムの概念では、クラスFooは自分の子供について何も知る必要がないため、前提条件は存在しません。しかし、親クラスのユーザーは、クラスを使用するために子クラスを知る必要はありません。この矛盾を解決するには?
  3. この種のことを Doxygen で文書化する特定の(/最良の)方法はありますか?
4

1 に答える 1

1

継承の存在下でコントラクトを使用する一般的なルールは次のとおりです。

  1. 前提条件は、子孫でのみ弱くなる可能性があります。
  2. 事後条件は、子孫でのみ強くなる可能性があります。

これにより、祖先クラスのクライアントが子孫によって提供される実装の影響を受けないことが保証されます。これは、このクライアントがそのような子孫の存在を必ずしも認識しておらず、祖先クラスで宣言されたメンバーに自由にアクセスできる必要があるためです。実行時に、対応する式が子孫クラスのオブジェクトに動的にバインドされます。

これは、ほとんどの状況でうまく機能します。しかし、あなたの例では、祖先クラスには前提条件がtrueあり、子孫クラスにはより強い前提条件があります。これに対処するには、いくつかのアプローチがあります。

  1. より強い前提条件を祖先クラスに持ち上げます。そこでの実装が考えられるすべてのケースに対処する場合でも、特定のシナリオでは重要な前提条件が予想される場合があり、クライアントはそれに対処する必要があります。
  2. すべてのケースを処理し、より強力な前提条件を完全に削除するように、子孫クラスの実装を変更します。
  3. 強い前提条件を持つ新しいメソッドを祖先クラスに追加するか、子孫クラスで別のメソッドを使用して、問題のメンバーの再宣言を回避し、元のメソッドとは関係がないようにします。
  4. 抽象的な契約を使用します。コントラクトを直接指定する代わりに、祖先クラスで定義された仮想関数を呼び出すことができます。あなたの例では、関数は を返しtrueます。子孫クラスは、この関数を再定義し、必要なチェックを追加できます。このソリューションの主な欠点は、クライアントがメソッドを呼び出す前に抽象的な前提条件をチェックして、それが違反していないことを確認する必要があることです。

最終的な決定は、具体的なシナリオによって異なります。

于 2016-06-08T08:33:07.237 に答える