web-dev-qa-db-ja.com

コンストラクターで大きなファイルを読み取るのは悪い習慣ですか?

したがって、私はC++で英語のトライデータ構造の実装を作成しようとしています。 TrieおよびTrieNodeクラスを作成しました。 TrieNodeクラスは、そのコンストラクターでvector<string>を受け取ります。これは、トライを構成する単語のリストです。

このvector<string>を取得するための私の解決策は、EnglishWordsListGeneratorクラスを使用することでした。このクラスは、コンストラクターで、おそらく有効な英語の単語のリストを含むファイルのファイル名の文字列を受け取ります。 (これまで人気のあったenable1.txtファイル)。

私はC++を初めて使用するので、オブジェクトを初期化するときに、コンストラクターから直接ファイルを読み取るのが良い方法と見なされるかどうかはわかりません。結局のところ、操作が失敗した場合はどうなりますか?一方、BjarneがRAIIを強く要求していることは知っています。ここで正しいことは何ですか?

7
Bassinator

コンストラクタのシグネチャを持つクラスを見るとき

 EnglishWordsListGenerator(const std::string &wordFileName)

このコンストラクターが指定されたファイルを読み取ることは明らかであるため(そして時間が必要です)、呼び出し元がこれからの可能な例外に注意する必要があることを理解するのは難しくありません(ファイルIOは失敗する可能性があります。そのため、他の回答が言っていることにもかかわらず、このデザインは大丈夫ですです。

誤解しないでください。将来的には、この設計ではもはや十分ではない要件が得られる可能性がありますが、この単純なインターフェースと動作がプログラムのすべての要件である限り、KISSとYAGNIの原則。「念のため」に別個の初期化メソッドを提供することで、複雑化を防ぎます。

16
Doc Brown

C++を初めて使用する場合は、コンストラクターで大きな作業を行うことを避けた方がよい場合があります。コンストラクターは言語の動作に密接に関係しており、コンストラクターが予期せず呼び出されることを本当に望まない(explicitはあなたの友達です!)。コンストラクタも値を返すことができないという点でかなりユニークです。これにより、エラー処理がより複雑になる可能性があります。

この種の推奨事項は、C++や、コンストラクターが呼び出される原因となる可能性のあるすべてのことについて学ぶほど重要ではなくなります。完璧な世界では、正しい答えは、ライブラリはユーザーが常に望んでいたことを正確に実行する必要があるということです。多くの場合、C++では、コンストラクター内で物事を行うことによって実装するのが最適です。ただし、コンストラクターを十分に理解して、ユーザーを隅に追い込まないようにする必要があります。ユーザーがコンストラクターを呼び出すつもりがなかったために呼び出されたためにプログラムが正常に動作しない理由のデバッグは、骨の折れる作業です。

同様に、例外が発生するスコープは、変数を使用する場所ではなく、変数が作成されるスコープと絡むため、コンストラクターでの例外処理はより困難になる可能性があります。例外が発生すると、例外が激怒する可能性がありますbeforemain()が実行され、ユーザーの例外を処理する機能が拒否されます。これは、グローバル変数を使用していて、エラーがわかりにくい場合に発生する可能性があります。

両極端の中間に位置する1つのオプションは、ファクトリーメソッドを使用することです。ユーザーがファクトリメソッドを使用することを期待している場合、その時点で少し余分な初期化を行うことができます。ファクトリーメソッドは、コンストラクターのように誤って呼び出されることはないため、混乱させることは困難です。

コンストラクターで作業を行うことの結果を理解していると確信したら、それは強力なツールになります。コンストラクタで作業を行う最終的な例は、_lock_guard_クラスです。ロックガードは、ミューテックスなどのロックを渡すことによって構築されます。彼らはコンストラクター(実際の作業を行う)中に.lock()を呼び出し、デストラクター中に.unlock()を呼び出します。

これらのクラスは、そのように使用するように設計されているため、コンストラクターで実際の作業を行う必要はありません。 _lock_guard_を使用する人は誰でも、コンストラクタが機能することをよく知っています。

_{
    lock_guard guardMe(myLock); // locks myLock
    doSomeStuff();
    doMoreStuff();
    // as I leave this block, guardMe will unlock myLock.  It will do so
    // even if I leave via an exception!
}
_

_lock_guard_クラスの実装を見ると、それらの注意の80%以上が、驚きを防ぐためにコンストラクターを正しく管理する方法に焦点を当てていることが容易にわかります。適切に実装されていれば、非常に強力です。不適切に実装された_lock_guards_は、実際には別のことをするように見えるため、存在の悩みの種になる可能性があります。

7
Cort Ammon

コンストラクターでファイルからオブジェクトをロードすることはお勧めできません。

他の回答の優れたC++引数を超えて、クリーンなコード原則を追加します:関数は1つのことだけを行う必要があります。わかりました。コンストラクタは通常の関数ではありませんが、原則は適用されます。

私は以下を提案します:

  • 再利用を目的として、Trieをできる限り一般的にします。それを使用できる他の多くのアプリケーションがあります。
  • あなたがそれを養う方法から独立させてください:理想的には。たとえば、stringまたはistreamからフィードするなど、アプリケーションごとに異なるアプローチを使用できます。結局のところ、アプリの将来のバージョンでは、異なる言語のデータベースから単語を取得する可能性があります。
  • ビルダーデザインパターン を使用して、構築プロセスの各部分を組み立てます。次に、ビルダーは、何らかの一般的なアプローチを使用して、ソースからトライをロードします(たとえば、トライを作成し、ソースを開き、ソースデータを反復処理してトライに挿入します。トライが完了すると、トライを返します)。次に、ファイルからロードするための具体的なビルダーを作成し、他の代替を簡単に選択できます。
5
Christophe

いくつかの理由で、コンストラクターでファイルを読み取ることはないと思います。

  1. オブジェクトの作成と、場合によってはプログラムの起動が大幅に遅くなります。
  2. あなたが述べたように、エラー処理のための限られたオプションがあります。プログラム全体をtry ... catchブロックに入れるか、何か問題が発生した場合は何らかのフラグを設定する必要があります。
  3. それを複数回読む必要がある場合はどうなりますか?コードを複製するか、コンストラクターが呼び出す関数にコードを分割する必要があります。

他の理由があると私は確信しています。それらは私の頭に浮かんだ最初の理由にすぎません。ファイルが小さく、一度しか読み取っていない場合は、おそらく問題ありません。

1
bluegreen