web-dev-qa-db-ja.com

ポリシーベースの設計とベストプラクティス-C ++

struct InkPen
{
  void Write()
  {
    this->WriteImplementation();
  }

  void WriteImplementation()
  {
    std::cout << "Writing using a inkpen" << std::endl;
  }

};

struct BoldPen
{
  void Write()
  {
    std::cout << "Writing using a boldpen" << std::endl;
  }
};

template<class PenType>
class Writer : public PenType
{
public:
  void StartWriting()
  {
    PenType::Write();
  }
};

int main()
{
  Writer<InkPen> writer;
  writer.StartWriting();
  Writer<BoldPen> writer1;
  writer1.StartWriting();
  return 0;
}

私は学習の一環として上記のコードを書きましたポリシーベースの設計。上記のコードについていくつか質問があります

1-この実装は正しいように見えますか?つまり、それは本当にポリシーベースの設計のように見えますか?

2-あらゆる種類のペンをライターに引っ掛けることができるようになりました。しかし、デフォルトのコンストラクターがない(パラメーター化されたコンストラクターのみ)ペンを入手した場合はどうすればよいですか?この状況にどのように対処しますか?

template<class PenType>
class Writer : public PenType
{
public:
  void StartWriting()
  {
    PenType::Write();
  }
};

3-上記のコードが次のように使用される場合

Writer<InkPen> writer;

コンパイラはPenTypeInkPenに置き換えると思います。はいの場合、基本クラス名の前に付ける代わりに、Write() from StartWriting()だけを呼び出すことができないのはなぜですか(PenType :: Write())?

4-ポリシーベースの設計では、意味的に無効なクラスから派生する必要があると思います。上記のコードでは、ライターがペンを使用しているという理由だけで、ライターはペンから派生しています。しかし、作家がペンであると言うことは意味的に無効です。これに対処する他のより良い方法はありますか、または私はここで何かが欠けていますか?

何かご意見は?

24
Navaneeth K N

クラスを実装する方法は次のとおりです。

template<class PenType>
class Writer
{
public:
  Writer(const PenType& pen = PenType()) : pen(pen) {}

  void StartWriting()
  {
    pen.Write();
  }

private:
  PenType pen;
};

これにより、デフォルトのコンストラクターがない場合、またはコンストラクターを使用したくない場合に、ユーザーは特定のPenオブジェクトをコンストラクターに渡すことができます。次に、次の場合は、PenTypeオブジェクトを省略できます。 'デフォルトのコンストラクターで作成できるようにしてください。 C++標準ライブラリは、多くのクラスで同じことを行います(たとえば、コンテナクラスのアロケータを考えてみてください)。

継承を削除しました。実際には何も追加されていないようです(問題が発生する可能性があります。WriterクラスのユーザーがPenType :: Write関数を直接呼び出さないようにする必要があります。代わりにプライベート継承を使用できますが、多くの場合、構成はよりシンプルで従来型のデザイン。

一般に、ポリシーベースの設計は継承を必要としません。メンバーとして追加することも同様に機能します。継承を行う場合は、#4で説明した問題が発生しないように、非公開にします。

24
jalf

これは、ポリシーベースのスマートポインタ実装の良い例のように見えます: linkAndrei Alexandresc は、彼の著書の1つでポリシーベースのスマートポインターの実装について説明しています。今あなたの質問について。私はこのようなことでいくらかの経験を持っていますが、私の言葉を当然のことと思うには十分ではありません:

広告1と4。ポリシーベースの設計は、継承よりもテンプレートに関するものだと思います。テンプレートクラスを作成すると、テンプレート引数は次のようなポリシークラスになります。

template<class FooPolicy, class BarPolicy>
class Baz {
    // implementation goes here
};

次に、クラスのポリシークラスのメソッドを使用します。

void Baz::someMethod(int someArg) {
    FooPolicy::methodInit();
    // some stuff
    BarPolicy::methodDone();
}

多くの場合、ポリシーは状態を必要としないため、この例では静的メソッドを使用します。含まれている場合は、継承ではなく、構成によってポリシーの状態を組み込みます。

template<class FooPolicy, class BarPolicy>
class Baz {
  private:
    FooPolicy::State fooState; // might require 'typename' keyword, I didn't
                               // actually tried this in any compiler
    // rest of the Baz class
};

広告2.テンプレートの特殊化を書くことができます-メインクラスとそのポリシーの特定の組み合わせに対して、任意のメソッドまたはコンストラクターの特別なバージョンを書くことができます、AFAIK:

template <>
Baz<SomeConcreteFooPolicy, SomeConcreteBazPolicy>::Baz(someArgument)
   : fooState(someArgument)
{
    // stuff here
}

それがあなたに少し役立つことを願っています、

マイク

13
Jasiu

1-この実装は正しいように見えますか?つまり、それは本当にポリシーベースの設計のように見えるのでしょうか?

ポリシークラスは、動作を組み合わせてさまざまな組み合わせを生成することからその有用性を引き出します。このような単一のテンプレートパラメータがある場合、それはポリシークラスではありません。

2-あらゆる種類のペンをライターに引っ掛けることができるようになりました。しかし、デフォルトのコンストラクターがない(パラメーター化されたコンストラクターのみ)ペンを入手した場合はどうすればよいですか?この状況にどのように対処しますか?

繰り返しますが、これはポリシークラスの奇妙な例です。ただし、質問に直接答えるために、PenTypeを受け入れるコンストラクターを提供できます。また、PenTypeからの継承を避け、代わりにメンバーとして保存する必要があります(ポリシークラスをそのポリシーと緊密に結合する必要はありません)。

コンパイラはPenTypeをInkPenに置き換えると思います。はいの場合、基本クラス名(PenType :: Write())の前に付ける代わりに、StartWriting()からWrite()だけを呼び出すことができないのはなぜですか?

クラステンプレートから継承する場合は、this-> memberまたはBaseClass :: memberを指定する必要があります。

4-ポリシーベースの設計では、意味的に無効なクラスから派生する必要があると思います。上記のコードでは、ライターがペンを使用しているという理由だけで、ライターはペンから派生しています。しかし、作家がペンであると言うことは意味的に無効です。これに対処する他のより良い方法はありますか、または私はここで何かが欠けていますか?

上記のように、PenTypeをメンバーとして保存します。継承の緊密な結合関係を回避するため、継承よりも構成を常に優先します。

5
stinky472

私はこのスレッドが古いことを知っていますが、最初の投稿に大きな欠陥があり、このスレッドはグーグルのトップの結果の1つです...そう:

ポリシーベースの設計にpublic継承を使用しないでください!これは、「has-a」/「uses-a」ではなく「is-a」と言います。したがって、private継承を使用する必要があります!

3
D.R.