Cにレイジー/多段構造++
-
23-09-2019 - |
質問
は、多段階の建設のための優れた既存のクラス/デザインパターン何/ C ++でのオブジェクトの初期化?
私は、プログラムの流れに異なるポイントで初期化されなければならないいくつかのデータメンバを持つクラスを持っているので、彼らの初期化が遅れることがあります。例えば、一つの引数は、ファイルやネットワークから別から読み取ることができます。
現在、私は、データメンバの遅延建設のためのブースト::オプションを使用していますが、オプションでは、遅延構築よりも意味的に異なっていることを私を悩まいます。
私は必要なもの::ブーストの機能を思い出させる、バインドし、ラムダ関数の部分適用、およびこれらのライブラリを使用して、私はおそらく、多段構造を設計することができます - しかし、私は、既存の、テストクラスを使用して好みます。 (それとも私が精通していないです別の多段建設パターンがあります)。
解決
重要な問題は、あなたがのタイプレベルでのの不完全人口オブジェクトから完全に人口のオブジェクトを区別する必要があるかどうかではありません。あなたは区別をしないように決定した場合、あなたがやっているように、そしてちょうどboost::optional
または類似を使用します。これは、簡単に素早くコーディングを取得することができます。 OTOHあなたは、特定の機能が完全に移入されたオブジェクトを必要とするという要件を適用するようにコンパイラを取得することはできません。あなたは、フィールドのたびにチェック実行時に実行する必要があります。
パラメータグループタイプ
あなたはタイプレベルで不完全人口オブジェクトから完全に取り込まれたオブジェクトを区別しない場合は、は、あなたが関数は、完全なオブジェクトが渡されることを要件を適用することができます。これを行うには、私はそれぞれの関連タイプXParams
に対応するタイプのX
を作成することをお勧め。 XParams
は、初期構築後に設定することができ、各パラメータのboost::optional
メンバーとセッター機能を持っています。そして、あなたはその唯一の引数および必要な各パラメータがそのX
オブジェクトの内部設定されていることを確認してXParams
を取る唯一の(非コピー)コンストラクタを持っているXParams
を強制することができます。 (わからない場合は、このパターンに名前がある - ?で私たちを埋めるために、この編集したいと誰でも)
"部分オブジェクト" タイプ
それは完全に(バックフィールドの値を取得するような些細なものよりもおそらく他)が取り込まれる前に、あなたが本当にオブジェクトとの行うの何にも持っていない場合は、これは見事に動作します。あなたは時々、「フル」X
のような不完全人口X
を処理する必要がない場合は、代わりに、テストに必要なすべてのフィールドがあるかどうかという前提のテストを実行するためのすべてのロジックが含まれているタイプのX
、からXPartial
派生、プラスprotected
仮想メソッドを作ることができます人口。それだけで今まで完全に、人口の状態で構築することができることをX
の性を保証場合そしてそれは常にtrue
を返す些細なチェックをして、それらの保護されたメソッドをオーバーライドすることができます:
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!
};
//:は、それがここに継承を見えるかもしれないが、このアプローチに従うので、<のhref = "HTTP、X
が安全にどこでもXPartial&
がために頼まれる供給することができるこの方法の手段でそれをやって、「前に戻る」でありますen.wikipedia.org/wiki/Liskov_substitution_principle」のrel = "nofollowをnoreferrer">リスコフの置換原則に。この手段の機能は、それが部分的に移入オブジェクト処理でき示すために完全X&
オブジェクト、またはX
を必要示すためXPartial&
のパラメータの型を使用することができる - 。XPartial
オブジェクトまたは完全X
いずれかを渡すことができ、その場合には
もともと私はisComplete()
としてprotected
を持っていましたが、X
のコピーCTORと代入演算子は、そのXPartial&
引数でこの関数を呼び出す必要があり、彼らは十分なアクセス権を持っていないので、これは動作しませんでした。反射では、それが公にこの機能を公開する方が理にかなっています。
他のヒント
私はここで何かが欠けする必要があります - 私はこの種のものにすべての時間を行います。これは大きなおよび/または全ての状況で、クラスで必要とされないオブジェクトを持っていることは非常に一般的です。だから、動的に作成!
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でなければならない、とビッグ・ポインタは、おそらく変更可能でなければなりません。そしてもちろん、Aは、他の例に含まれるクラスのコンストラクタのパラメータをキャッシュ意味するかもしれビッグを構築することができなければなりません。また、コピー/割り当てポリシーを決定する必要があります - 私はおそらくクラスのこの種の両方を禁止したい。
。私は、この特定の問題に対処するために、任意のパターンを知りません。これは、トリッキーなデザインの質問、およびC ++のような言語に幾分ユニークなひとつです。もう一つの問題は、この質問への答えが密接にあなたの個人(または企業)のコーディングスタイルに結びついているということです。
私は、これらのメンバーのためにポインタを使用して、彼らが構築する必要がある場合、それらを同時に割り当てます。あなたはこれらのためにauto_ptrを使用し、それらが初期化されるかどうかを確認するためにNULLに対してチェックすることができます。 (私はポインタを考えるには、内蔵されている「オプション」タイプC / C ++ / Javaの、NULLが有効なポインタではありません、他の言語がある中で)ます。
スタイルの問題として、1つの問題は、あなたがあまりにも多くの仕事を行うには、あなたのコンストラクタに頼ることができることです。私はOOをコーディングしていたとき、私はコンストラクタは一貫した状態でオブジェクトを取得するだけで十分な仕事を持っています。私は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
}
あなたがこの技術を使用するための固体の理由を持っていない限り、このよう無謀として何かをすることは、あなたの仲間のプログラマーの軽蔑を稼ぐかもしれないことに注意してください。また、これはちょうどあなたが後でそれを再び呼び出すことができ、コンストラクタを遅らせることはありません。
いくつかのユースケースのための良い解決策のようboost.optionalルックスを使用します。私はあまりコメントすることはできませんので、私はそれをあまりプレイしていません。このような機能を扱うときに、私は心に留めておくことの一つは、私が代わりにデフォルトとコピーコンストラクタのオーバーロードされたコンストラクタを使用できるかどうかです。
私は、このような機能が必要な場合は、私はちょうどこのように必要なフィールドの型へのポインタを使用することになります:
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のアクセスセマンティクス
それはあなたが本当にあなたが選んだの瞬間まで、オブジェクトの建設を遅らせることができますを除いて、私が知っている最も簡単な方法は、ディートリッヒエップによって提案された手法に似てます。
基本的には:予備malloc関数を使用して、オブジェクトの代わりに、新しい(それによって、コンストラクタをバイパスし)、その後、オーバーロードされた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ベースのアロケータを使用していたとき、個人的に、私が今までこの構文を使用した唯一の時間でした。ディートリッヒが示唆するように、あなたは本当に、本当にコンストラクタの呼び出しを遅らせる必要があるかどうかを疑問視すべきです。 のベースの必要に応じて他のオーバーロードコンストラクタは、より多くの作業を行うことができる一方で、コンストラクタは、修理可能な状態にあなたのオブジェクトを取得するために最低限を実行する必要があります。
私は知りません。私はそれを見てきた場所で、私たちは、「オンデマンド」「オンデマンド」や、「怠惰」と呼んでます。