2012-03-19 10 views
7

私は現在、コードベースのすべての悪い習慣を見つけ、仲間に害のあるコードを修正するよう説得しています。私の洞窟探検中に、私はここで多くの人が次のパターンを使用していることに気づい:コンストラクタではなく "initialize()"メソッドに対する引数

class Foo 
{ 
    public: 
    Foo() { /* Do nothing here */ } 
    bool initialize() { /* Do all the initialization stuff and return true on success. */ } 
    ~Foo() { /* Do all the cleanup */ } 
}; 

今、私は間違っているかもしれませんが、私には、このinitialize()方法の事はひどいです。私はそれがコンストラクターを持つことの全目的を取り消すと信じています。

私は同僚にこのデザイン決定がなされた理由を尋ねると、スローなしでコンストラクタを終了することができないので、常に選択肢がないと答えています(スローするとはは常にです)。

私は貴重な議論が不足していると私は認めています。だからここに私の質問があります:私はこの構造が痛みであると思います。 ?

ありがとうございます。

+1

FTRが、これは2相の初期化として知られています。 –

+1

ここに明白な疑問があります:建設が失敗した場合、あなたの同僚は途中で作られたオブジェクト*で何をしたいのですか?無駄だ。それはまた、建設の例外を投げたかもしれません – jalf

+0

@ jalf:確かに良い点。 – ereOn

答えて

8

個人的に私は、どちらか一方を除いても間違いであると感じますが、ただし、あなたの規約で例外の使用を完全に禁止すると、失敗するコンストラクタの単一ステップの初期化が禁止されます。

一般に、オブジェクトは より強い不変量を持つ可能性があるため、オブジェクトが「初期化されていない」状態に存在することが可能であることを意味があるか、または有用であると考える場合、2段階の初期化のみを使用します。

2段階の初期化では、オブジェクトが初期化されていない状態にあることが有効です。したがって、オブジェクトで動作するすべてのメソッドは、初期化されていない状態である可能性を認識して正しく処理する必要があります。これはポインタを使った作業に似ていますが、ポインタがNULLではないと仮定するのは貧弱な形式です。逆に、コンストラクタ内で初期化をすべて行い、例外を発生させた場合、「オブジェクトは常に不変式のリストに初期化されます」というように、 はオブジェクトの状態についてより簡単で安全になります。

2

これは通常Two PhaseまたはMultiphase Initializationと呼ばれ、コンストラクタコールが正常に終了すると、すぐに使用できるオブジェクトが必要になるため、特に悪いことです。この場合は、オブジェクトを使用します。

私は次のことをさらに強調することはできません。
失敗した場合にコンストラクタから例外をスローすることは、オブジェクト構築の失敗を処理する最も簡単で唯一の方法です。

+0

を参照してください。 – maxschlepzig

0

場合によって異なります。

コンストラクタがいくつかの引数のために失敗する可能性がある場合は、例外をスローする必要があります。しかし、もちろん、コンストラクタから例外をスローすることについて自分自身を文書化する必要があります。

Fooにオブジェクトが含まれている場合、それらはコンストラクタで1回、initializeメソッドで1回、2回初期化されるため、欠点です。

IMOの最大の欠点は、initializeに電話することです。オブジェクトが無効な場合、そのオブジェクトを作成するポイントは何ですか?

したがって、コンストラクタから例外をスローしたくないという唯一の引数がある場合、それはかなり悪い引数です。

ただし、何らかの遅延初期化が必要な場合は有効です。

0

これは痛みですが、コンストラクタから例外をスローしないようにしたい場合は、他に選択肢がありません。同様に苦痛を伴う別のオプションもあります。コンストラクタですべての初期化を行い、オブジェクトが成功裏に構築されたかどうかをチェックする必要があります(たとえば、変換演算子をboolにするか、IsOKメソッド)。人生は難しく、.....あなたは死ぬ:(

+0

しかし、オブジェクトを初期化できない(したがって構築できなかった)場合は、それはどういう意味ですか?私には 'IsOK'メソッドの問題も同様に間違っています。 – ereOn

+0

コンストラクタでは、すべてのメンバーを初期値に設定するだけです(すべてが定義された状態になっている)ので、 'initialize'を呼び出して(潜在的に)例外をスローすることができます。コンストラクタの直後に 'IsOK'(または演算子bool)がfalseを返します。オブジェクトは、常に初期(既知の)値に初期化することができます。私はそれが良いアイデアであると主張しているわけではない。時には時にはそれが唯一の正気な選択肢でもある。 BTW:初期化リストのコンストラクタからスローされた例外をキャッチしたい場合 - 構文が実際には醜いものになります - 両方の悪からIsOK/oper_boolを選択します。 – sirgeorge

+0

しかし、オブジェクトが構築された後、 'initialize()'を呼び出す前に、オブジェクトは何を表していますか? – ereOn

1

これは、オブジェクトのセマンティクスによって異なります。初期化がクラス自体のデータ構造にとって重要なものである場合、コンストラクタから例外をスローするか(たとえば、メモリ不足の場合)、またはアサーションによって例外を処理する方が良いでしょう。あなたのコードは実際には失敗してはいけません)。

一方、構成の成功またはその他がユーザーの入力に依存する場合、障害は例外的な条件ではなく、テストする必要がある通常の予想される実行時動作の一部です。その場合は、オブジェクトを「無効な」状態で作成するデフォルトのコンストラクタと、コンストラクタ以降で呼び出すことができ、成功するかどうかを示す初期化関数を用意する必要があります。例としてstd::ifstreamとする。

だからあなたのクラスのスケルトンは次のようになります。

class Foo 
{ 
    bool valid; 
    bool initialize(Args... args) { /* ... */ } 

public: 
    Foo() : valid(false) { } 
    Foo(Args... args) : valid (false) { valid = initialize(args...); } 

    bool reset(Args... args) // atomic, doesn't change *this on failure 
    { 
     Foo other(args...); 
     if (other) { using std::swap; swap(*this, other); return true; } 
     return false; 
    } 

    explicit operator bool() const { return valid; } 
}; 
関連する問題