web-dev-qa-db-ja.com

コンストラクターでどのくらいの作業を行う必要がありますか?

時間がかかる可能性のある操作をコンストラクターで実行する必要がある場合、またはオブジェクトを作成して後で初期化する必要がある場合。

たとえば、ディレクトリ構造を表すオブジェクトを構築する場合、オブジェクトとその子の作成はコンストラクタで行う必要があります。明らかに、ディレクトリにはディレクトリを含めることができ、ディレクトリにはディレクトリなどを含めることができます。

これに対するエレガントな解決策は何ですか?

50
mintydog

歴史的に、コンストラクターメソッドが完了するとオブジェクトを使用できるようにコンストラクターをコーディングしてきました。含まれるコードの量または量は、オブジェクトの要件によって異なります。

たとえば、次のCompanyクラスを詳細ビューに表示する必要があるとします。

public class Company
{
    public int Company_ID { get; set; }
    public string CompanyName { get; set; }
    public Address MailingAddress { get; set; }
    public Phones CompanyPhones { get; set; }
    public Contact ContactPerson { get; set; }
}

会社に関するすべての情報を詳細ビューに表示したいので、コンストラクターには、すべてのプロパティにデータを入力するために必要なすべてのコードが含まれます。これが複合型である場合、Companyコンストラクターは、Address、Phones、およびContactコンストラクターの実行もトリガーします。

ここで、CompanyNameとメインの電話番号のみが必要なディレクトリリストビューにデータを入力する場合、その情報のみを取得し、残りの情報を空のままにするクラスに2番目のコンストラクターがある可能性がありますまたはその情報のみを保持する別のオブジェクトを作成するだけかもしれません。それは実際には、情報がどのように、どこから取得されるかに依存します。

クラスのコンストラクターの数に関係なく、私の個人的な目標は、オブジェクトに課せられる可能性のあるタスクに備えてオブジェクトを準備するために必要な処理を行うことです。

3
Neil T.

要約する:

  • 少なくとも、コンストラクターは、その不変条件が真になるようにオブジェクトを構成する必要があります。

  • 不変条件の選択はクライアントに影響を与える可能性があります(オブジェクトは常にアクセスの準備ができていると約束しますか?それとも特定の状態でのみですか?)事前にすべてのセットアップを処理するコンストラクターは、作業を簡単にする可能性がありますクラスのクライアントのために。

  • 長時間実行されるコンストラクターは本質的に悪いわけではありませんが、状況によっては悪い場合があります。

  • ユーザーの操作を伴うシステムの場合、どのタイプの長時間実行メソッドでも応答性が低下する可能性があるため、回避する必要があります。

  • コンストラクターが終了するまで計算を遅らせることは、効果的な最適化になる可能性があります。すべての作業を実行する必要がない場合があります。これはアプリケーションによって異なり、時期尚早に決定されるべきではありません。

  • 全体的に、それは異なります。

48
Oddthinking

通常、コンストラクターに計算を行わせたくありません。コードを使用している他の誰かは、それが基本的なセットアップ以上のことをすることを期待しません。

あなたが話しているようなディレクトリツリーの場合、「エレガントな」解決策は、オブジェクトの構築時に完全なツリーを構築しないことです。代わりに、オンデマンドで構築してください。オブジェクトを使用している人はサブディレクトリの内容を気にしないかもしれないので、コンストラクターに最初のレベルをリストさせることから始め、誰かが特定のディレクトリに降りたい場合は、要求されたときにツリーのその部分を構築しますそれ。

24
SoapBox

必要な時間は、コンストラクターに何かを入れない理由であってはなりません。コード自体をプライベート関数に入れ、コンストラクターからそれを呼び出して、コンストラクター内のコードを明確に保つことができます。

ただし、オブジェクトに定義済みの条件を与えるために実行したいことが必要でなく、後で最初に使用するときにそのようなことを実行できる場合、これは後で実行するための妥当な引数になります。ただし、クラスのユーザーに依存させないでください。これらのこと(オンデマンドの初期化)は、クラスのユーザーに対して完全に透過的である必要があります。そうしないと、オブジェクトの重要な不変条件が簡単に壊れてしまう可能性があります。

それは異なります(典型的なCSの答え)。実行時間の長いプログラムの起動時にオブジェクトを作成する場合は、コンストラクターで多くの作業を行うことに問題はありません。これが高速応答が期待されるGUIの一部である場合は、適切でない可能性があります。いつものように、最良の答えは、最初に最も簡単な方法で試して、そこからプロファイリングして最適化することです。

この特定のケースでは、サブディレクトリオブジェクトの遅延構築を行うことができます。最上位ディレクトリの名前のエントリのみを作成します。それらにアクセスした場合は、そのディレクトリの内容をロードします。ユーザーがディレクトリ構造を説明するときに、これを再度実行します。

10
KeithB

コンストラクターの最も重要な仕事は、オブジェクトに初期の有効な状態を与えることです。私の意見では、コンストラクターに対する最も重要な期待は、コンストラクターに副次的な影響がないことです。

7
Hugo

コードの保守、テスト、およびデバッグのために、コンストラクターにロジックを配置しないようにしています。コンストラクターからロジックを実行したい場合は、ロジックをinit()などのメソッドに配置し、コンストラクターからinit()を呼び出すと便利です。単体テストの開発を計画している場合は、さまざまなケースをテストすることが難しい場合があるため、コンストラクターにロジックを配置することは避けてください。前のコメントはすでにこれに対処していると思いますが...アプリケーションがインタラクティブである場合は、顕著なパフォーマンスの低下につながる単一の呼び出しを避ける必要があります。アプリケーションが非対話型の場合(例:毎晩のバッチジョブ)、単一のパフォーマンスへの影響はそれほど大きな問題ではありません。

4
rich

長時間実行されるコンストラクターは本質的に悪いものではないことに同意します。しかし、私はあなたがほとんどの場合間違ったことをしていると主張します。私のアドバイスは、Hugo、Rich、Litbのアドバイスと似ています。

  1. コンストラクターで行う作業を最小限に抑えます。状態の初期化に重点を置きます。
  2. 回避できない場合を除いて、コンストラクターからスローしないでください。 std :: bad_allocのみをスローしようとします。
  3. OSまたはライブラリAPIは、それらが何をするのかを理解していない限り、呼び出さないでください。ほとんどの場合、ブロックできます。これらは開発ボックスとテストマシンですばやく実行されますが、フィールドでは、システムが他のことを行うのに忙しいため、長期間ブロックされる可能性があります。
  4. コンストラクターでI/Oを実行することは決してありません。 I/Oは通常、あらゆる種類の非常に長い遅延(数百ミリ秒から数秒)の影響を受けます。 I/Oにはが含まれます
    • ディスクI/O
    • ネットワークを使用するもの(間接的であっても)ほとんどのリソースはすぐに使用できることを忘れないでください。

I/O問題の例:多くのハードディスクには、数百ミリ秒、さらには数千ミリ秒の間、読み取りまたは書き込みを処理しない状態になるという問題があります。第1世代および第1世代のソリッドステートドライブはこれを頻繁に行います。ユーザーは、あなたのプログラムが少しの間ハングしたことを知る方法を持っています-彼らはそれがあなたのバグのあるソフトウェアだと思っているだけです。

もちろん、長時間実行されるコンストラクターの悪さは、次の2つのことに依存しています。

  1. 「長い」とは
  2. 与えられた期間に「long」コンストラクターを持つオブジェクトが構築される頻度。

さて、「long」が単に数100クロックサイクルの余分な作業である場合、それほど長くはありません。しかし、コンストラクターは数百マイクロ秒の範囲に入っています。かなり長いと思います。もちろん、これらの1つだけをインスタンス化する場合、またはそれらをめったにインスタンス化しない場合(たとえば、数秒ごとに1つ)、この範囲の期間のために問題が発生する可能性はほとんどありません。

頻度は重要な要素です。数個しか構築していない場合、500 us ctorは問題になりません。ただし、数百万個を作成すると、パフォーマンスに重大な問題が発生します。

あなたの例について話しましょう:「クラスディレクトリ」オブジェクト内のディレクトリオブジェクトのツリーにデータを入力します。 (注:これはグラフィカルUIを備えたプログラムであると想定します)。ここで、CTORの期間は、作成するコードに依存しません。つまり、任意の大きなディレクトリツリーを列挙するのにかかる時間の被告です。これはローカルハードドライブでは十分に悪いです。リモート(ネットワーク化された)再調達ではさらに問題があります。

ここで、ユーザーインターフェイススレッドでこれを行うことを想像してみてください。UIは数秒、数十秒、場合によっては数分の間、トラックで停止します。 Windowsでは、これをUIハングと呼びます。それらは悪い悪い悪いです(はい、私たちはそれらを持っています...はい、私たちはそれらを排除するために一生懸命働いています)。

UIハングは、人々があなたのソフトウェアを本当に嫌うようにすることができるものです。

ここで行う正しいことは、ディレクトリオブジェクトを初期化することです。キャンセル可能なループでディレクトリツリーを構築し、UIを応答状態に保ちます(キャンセルボタンは常に機能するはずです)

3
Foredecker

コンストラクターで行う必要のある作業の量については、物事の遅さ、クラスの使用方法、および一般的に個人的にどのように感じるかを考慮に入れる必要があると思います。

ディレクトリ構造オブジェクトについて:最近、HTPCにsamba(Windows共有)ブラウザを実装しましたが、非常に遅いため、実際にディレクトリにアクセスしたときにのみ初期化することを選択しました。例えば最初にツリーはマシンのリストだけで構成され、次にディレクトリを参照するたびに、システムはそのマシンからツリーを自動的に初期化し、ディレクトリリストを1レベル深く取得します。

理想的には、ディレクトリを幅優先でスキャンし、現在参照しているディレクトリを優先するワーカースレッドを作成することさえできると思いますが、一般的には、単純なものには手間がかかりすぎます;)

2
Frans-Willem

RAIIはC++リソース管理のバックボーンであるため、コンストラクターで必要なリソースを取得し、デストラクタで解放します。

これは、クラスの不変条件を確立するときです。時間がかかると時間がかかります。 「Xが存在する場合はYを実行する」構造が少ないほど、クラスの残りの部分の設計が簡単になります。後で、プロファイリングでこれが問題であることがわかった場合は、遅延初期化(最初に必要なときにリソースを取得する)などの最適化を検討してください。

2
christopher_f

コンストラクターの外部で何かを実行できる場合は、内部で実行しないでください。後で、クラスが他の点で行儀が良いことがわかった場合、内部でそれを行うリスクがあります。

1
Aydya

必要なだけ、それ以上はありません。

コンストラクターはオブジェクトを使用可能な状態にする必要があるため、少なくともクラス変数を初期化する必要があります。 inittedの意味は、幅広い解釈を持つことができます。これは不自然な例です。 Nを提供する責任があるクラスがあると想像してください!呼び出し元のアプリケーションに。

これを実装する1つの方法は、必要な値を計算して返すループを持つメンバー関数を使用して、コンストラクターに何もしないようにすることです。

それを実装する別の方法は、配列であるクラス変数を持つことです。コンストラクターはすべての値を-1に設定して、値がまだ計算されていないことを示します。メンバー関数は遅延評価を行います。配列要素を調べます。 -1の場合は計算して保存し、値を返します。それ以外の場合は配列から値を返します。

これを実装する別の方法は、最後の方法と同じです。コンストラクターのみが値を事前に計算し、配列にデータを入力するため、メソッドは配列から値を引き出して返すことができます。

これを実装する別の方法は、値をテキストファイルに保持し、値を取得するファイルへのオフセットの基礎としてNを使用することです。この場合、コンストラクターはファイルを開き、デストラクタはファイルを閉じますが、メソッドはある種のfseek/freadを実行して値を返します。

それを実装する別の方法は、値を事前に計算し、クラスが参照できる静的配列としてそれらを格納することです。コンストラクターは機能せず、メソッドは配列に到達して値を取得し、それを返します。複数のインスタンスがその配列を共有します。

とはいえ、注目すべきことは、通常、コンストラクターを1回呼び出してから、他のメソッドを頻繁に使用できるようにすることです。コンストラクターでより多くの作業を行うということは、メソッドの作業が少なくて済み、実行が速くなることを意味する場合、それは良いトレードオフです。ループのように大量に構築/破棄する場合は、コンストラクターに高いコストをかけることはおそらく良い考えではありません。

1
EvilTeach

Ctorが例外をスローする可能性のあることを何もしないことを確認してください。

1
EricSchaefer

すばらしい質問です。「Directory」オブジェクトが他の「Directory」オブジェクトへの参照を持っている場合の例も、すばらしい例です。

この特定のケースでは、コードを移動してコンストラクターから従属オブジェクトを構築し(または、ここで別の投稿が推奨するように、最初のレベル[即時の子]を実行し)、別個の「初期化」または「構築」メカニズムを使用します。

それ以外の場合は、パフォーマンスだけでなく、メモリフットプリントという別の潜在的な問題があります。非常に深い再帰呼び出しを行うと、メモリの問題も発生する可能性があります[スタックはすべてのローカル変数のコピーを保持するため再帰が終了するまで]。

0
monojohnny

それは実際にはコンテキスト、つまりクラスが解決しなければならない問題に依存します。たとえば、現在の子を常に表示できるようにする必要がありますか?答えが「はい」の場合、子はコンストラクターにロードされるべきではありません。一方、クラスがディレクトリ構造のスナップショットを表す場合は、コンストラクターにロードできます。

0
Jonas Kongslund

私はシンコンストラクターに投票し、その場合はオブジェクトに「初期化されていない」状態の動作を追加します。

理由:そうでない場合は、すべてのユーザーに重いコンストラクターも持たせるか、クラスを動的に割り当てるように強制します。どちらの場合も、面倒と見なされる場合があります。

コンストラクターはmain()の前に実行され、デバッガーがトレースするのがより困難になるため、静的になると、そのようなオブジェクトからエラーをキャッチするのは難しい場合があります。

0