8

C ++でのオブジェクトの多段階構築/初期化に適した既存のクラス/デザインパターンは何ですか?

プログラムのフローのさまざまな時点で初期化する必要があるいくつかのデータ メンバーを持つクラスがあるため、それらの初期化を遅らせる必要があります。たとえば、1 つの引数をファイルから読み取り、別の引数をネットワークから読み取ることができます。

現在、データメンバーの遅延構築にboost::optionalを使用していますが、optionalがdelay-constructedと意味的に異なることが気になります。

私が必要としているのは、boost::bind と lambda の部分関数アプリケーションの機能を思い出させるものであり、これらのライブラリを使用すると、おそらく多段階の構築を設計できますが、既存のテスト済みのクラスを使用することを好みます。(または、私がよく知らない別の多段階構築パターンがあるかもしれません)。

4

6 に答える 6

4

重要な問題は、タイプ レベルで完全に入力されたオブジェクトと不完全に入力されたオブジェクトを区別する必要があるかどうかです。区別しないことにしたboost::optional場合は、やっていることと同じように or を使用してください。これにより、コーディングをすばやく簡単に行うことができます。OTOH 特定の関数が完全に設定されたオブジェクトを必要とするという要件をコンパイラに強制させることはできません。毎回フィールドの実行時チェックを実行する必要があります。

パラメータ グループ タイプ

完全に設定されたオブジェクトと不完全に設定されたオブジェクトを型レベルで区別する場合は、関数に完全なオブジェクトを渡すという要件を強制できます。これを行うには、XParams関連する type ごとに対応する type を作成することをお勧めしXます。 初期構築後に設定できる各パラメーターのメンバーとセッター関数がありますXParams。次に、を唯一の引数として取り、必要な各パラメーターがそのオブジェクト内に設定されていることを確認する、1 つの (コピーではない) コンストラクターのみを持つようにboost::optional強制できます。(このパターンに名前があるかどうかはわかりません -- これを編集して記入してくれる人はいますか?)XXParamsXParams

「部分オブジェクト」タイプ

これは、オブジェクトが完全に取り込まれる前にオブジェクトに対して実際に何もする必要がない場合(おそらく、フィールド値を取得するなどの簡単なこと以外) はうまく機能します。X不完全なデータを "full" のように扱う必要がある場合はX、代わりにすべてのロジックと、必要なすべてのフィールドが入力されているかどうかをテストする前提条件テストを実行するための仮想メソッドを含むX型から派生させることができます。次に、完全に移入された状態でのみ構築できることが保証されている場合、それらの保護されたメソッドを、常に返す簡単なチェックでオーバーライドできます。XPartialprotectedXtrue

class XPartial {
    optional<string> name_;

public:
    void setName(string x) { name_.reset(x); }  // Can add getters and/or ctors
    string makeGreeting(string title) {
        if (checkMakeGreeting_()) {             // Is it safe?
            return string("Hello, ") + title + " " + *name_;
        } else {
            throw domain_error("ZOINKS");       // Or similar
        }
    }
    bool isComplete() const { return checkMakeGreeting_(); }  // All tests here

protected:
    virtual bool checkMakeGreeting_() const { return name_; }   // Populated?
};

class X : public XPartial {
    X();     // Forbid default-construction; or, you could supply a "full" ctor

public:
    explicit X(XPartial const& x) : XPartial(x) {  // Avoid implicit conversion
        if (!x.isComplete()) throw domain_error("ZOINKS");
    }

    X& operator=(XPartial const& x) {
        if (!x.isComplete()) throw domain_error("ZOINKS");
        return static_cast<X&>(XPartial::operator=(x));
    }

protected:
    virtual bool checkMakeGreeting_() { return true; }   // No checking needed!
};

ここでの継承は「後ろから前へ」のように見えるかもしれませんが、このように行うことは、Xan が要求された場所ならどこでも安全に供給できることを意味するXPartial&ため、このアプローチはLiskov Substitution Principleに従います。X&これは、完全なXオブジェクトが必要であることを示すために、または部分的に設定されたオブジェクトを処理できることを示すために、関数が のパラメーター タイプを使用できることを意味しますXPartial&。この場合、XPartialオブジェクトまたは完全なオブジェクトXを渡すことができます。

もともと私はisComplete()asを持っていましたが、のコピー ctor と代入演算子は引数でこの関数を呼び出す必要があり、十分なアクセス権がないため、これは機能しprotectedないことがわかりました。振り返ってみると、この機能を公開する方が理にかなっています。XXPartial&

于 2010-01-28T10:59:20.650 に答える
1

私はここで何かが欠けているに違いない - 私はいつもこの種のことをしている. すべての状況において、大きなオブジェクトやクラスで必要とされないオブジェクトを持つことは非常に一般的です。動的に作成してください。

struct Big {
    char a[1000000];
};

class A {
  public: 
    A() : big(0) {}
   ~A() { delete big; }

   void f() {
      makebig();
      big->a[42] = 66;
   }
  private:
    Big * big;
    void makebig() {
      if ( ! big ) {
         big = new Big;
      }
    }
};

makebig() はおそらく const (およびおそらくインライン) である必要があり、Big ポインターはおそらく変更可能であるべきであることを除いて、それよりも手の込んだものは必要ないと思います。もちろん、A は Big を構築できる必要があります。これは、含まれているクラスのコンストラクター パラメーターをキャッシュすることを意味する場合もあります。また、コピー/割り当てポリシーを決定する必要があります。おそらく、この種のクラスでは両方を禁止します。

于 2010-01-28T09:54:45.877 に答える
0

ユースケースによっては、boost.optional を使用するのが良い解決策のように見えます。私はそれであまり遊んでいないので、あまりコメントできません。このような機能を扱うときに心に留めていることの 1 つは、デフォルト コンストラクターやコピー コンストラクターの代わりに、オーバーロードされたコンストラクターを使用できるかどうかです。

このような機能が必要な場合は、必要なフィールドの型へのポインターを次のように使用します。

public:
  MyClass() : field_(0) { } // constructor, additional initializers and code omitted
  ~MyClass() {
    if (field_)
      delete field_; // free the constructed object only if initialized
  }
  ...
private:
  ...
  field_type* field_;

次に、ポインターを使用する代わりに、次の方法でフィールドにアクセスします。

private:
  ...
  field_type& field() {
    if (!field_)
      field_ = new field_type(...);
    return field_;
  }

const-access セマンティクスを省略しました

于 2010-01-28T08:57:37.683 に答える
0

私が知っている最も簡単な方法は、ディートリッヒ エップが提案した手法に似ていますが、選択した瞬間までオブジェクトの構築を真に遅らせることができます。

基本的に、new の代わりに malloc を使用してオブジェクトを予約し (コンストラクターをバイパスします)、配置 new を介してオブジェクトを本当に構築したい場合は、オーバーロードされた new 演算子を呼び出します。

例:

Object *x = (Object *) malloc(sizeof(Object));
//Use the object member items here. Be careful: no constructors have been called!
//This means you can assign values to ints, structs, etc... but nested objects can wreak havoc!

//Now we want to call the constructor of the object
new(x) Object(params);

//However, you must remember to also manually call the destructor!
x.~Object();
free(x);

//Note: if you're the malloc and new calls in your development stack 
//store in the same heap, you can just call delete(x) instead of the 
//destructor followed by free, but the above is the  correct way of 
//doing it

個人的には、この構文を使用したのは、C++ オブジェクトにカスタムの C ベースのアロケーターを使用しなければならなかったときだけでした。ディートリッヒが示唆するように、コンストラクターの呼び出しを本当に遅らせる必要があるかどうかを疑問視する必要があります。基本コンストラクターは、オブジェクトをサービス可能な状態にするために最低限のことを実行する必要がありますが、他のオーバーロードされたコンストラクターは、必要に応じてより多くの作業を実行する場合があります。

于 2010-01-28T09:14:57.187 に答える
0

これに正式なパターンがあるかどうかはわかりません。私が見た場所では、それを「レイジー」、「デマンド」、または「オンデマンド」と呼んでいました。

于 2010-01-28T11:10:48.293 に答える
0

この特定の問題に対処するためのパターンはわかりません。これはトリッキーな設計上の問題であり、C++ のような言語にやや特有のものです。もう 1 つの問題は、この質問に対する答えが、個人 (または企業) のコーディング スタイルに密接に関係していることです。

これらのメンバーにはポインターを使用し、構築する必要がある場合は同時に割り当てます。これらに auto_ptr を使用し、NULL に対してチェックして、初期化されているかどうかを確認できます。(ポインターは C/C++/Java の組み込みの「オプション」型であると思います。NULL が有効なポインターではない他の言語があります)。

スタイルの問題の 1 つは、コンストラクターに頼りすぎて多くの作業を行う可能性があることです。オブジェクト指向をコーディングしているときは、オブジェクトを一貫した状態にするのに十分な作業をコンストラクターに行わせます。たとえば、Imageクラスがあり、ファイルから読み取りたい場合、次のようにすることができます。

image = new Image("unicorn.jpeg"); /* I'm not fond of this style */

または、私はこれを行うことができます:

image = new Image(); /* I like this better */
image->read("unicorn.jpeg");

コンストラクターに大量のコードが含まれている場合、特に「コンストラクターが失敗するとどうなるか」という質問をすると、C++ プログラムがどのように機能するかを推論するのが難しくなる可能性があります。これは、コンストラクターからコードを移動する主な利点です。

もっと言いたいことがありますが、遅延建設で何をしようとしているのかわかりません。

編集:いつでもオブジェクトのコンストラクターを呼び出す (ややひねくれた) 方法があることを思い出しました。次に例を示します。

class Counter {
public:
    Counter(int &cref) : c(cref) { }
    void incr(int x) { c += x; }
private:
    int &c;
};

void dontTryThisAtHome() {
    int i = 0, j = 0;
    Counter c(i);       // Call constructor first time on c
    c.incr(5);          // now i = 5
    new(&c) Counter(j); // Call the constructor AGAIN on c
    c.incr(3);          // now j = 3
}

この手法を使用する明確な理由がない限り、これほど無謀なことをすると、仲間のプログラマーから軽蔑される可能性があることに注意してください。これもコンストラクターを遅らせることはなく、後でもう一度呼び出すことができます。

于 2010-01-28T08:42:25.627 に答える