C++でスタックを設計する場合、スタックが空のときにpop()メソッド(またはfront()メソッド)は何を返す必要がありますか?次のデザインのどれが良いですか?
OK、私の質問はそれほど明確ではないことがわかりました。書き直してみましょう。
スタック、キューなどのリンクリストに基づいて実装できるデータ構造がいくつかあり、それぞれにフロント要素(またはテール)を返すメソッドがあります。
知りたいのですが、データが空の場合のような方法の設計に関する原則的なガイドラインはありますか。
そして、私のより良い定義は、「正しく使いやすく、間違って使いにくい」ということです。
コントラクトによるプログラミングスタイルは、空でないスタックを持つことは、pop
を呼び出すことの前提条件であり、前提条件を満たさないメソッドには、ndefinedの結果があります。私の実装はstd::logic_error
をスローしますが、それは必須ではありません。 Cでは、私の実装はabort
経由でassert
になります。
pop
の呼び出し元は、pop
を呼び出す前に、スタックが空ではないという優先順位が保持されていることを確認する責任があります。したがって、スタックには、呼び出し元がチェックするためのisEmpty
メソッドが必要です。
C++ STLのactuallは、オブジェクトの値を返すことと、実際にスタックの内部データ構造からオブジェクトをポップすることを切り離して、2つの別個の関数にするため、pop()
を介して何も返しません。したがって、これは、スタックデータ構造の設計で考慮すべきもう1つのオプションです。
3番目のオプションも、これらのタイプのデータ構造に対するかなり慣用的なアプローチです。
「一意の空の要素」ではなく、4番目のオプションについては、実際には3番目のオプションのバリエーションを実行します。ここで、pop()
関数は、参照型ではなくポインター引数を取り、存在する場合はNULLを返します。スタックにオブジェクトが残っていません。
スタックのSGI STL実装 には次のデザインノートがあります:
Pop()がvalue_typeではなくvoidを返すのはなぜか疑問に思うかもしれません。つまり、1つのメンバー関数で2つを組み合わせるのではなく、top()とpop()を使用してtop要素を調べて削除する必要があるのはなぜですか?実際、この設計には正当な理由があります。 pop()が最上位の要素を返した場合、参照ではなく値で返す必要があります。参照で返すと、ダングリングポインタが作成されます。ただし、値による戻りは非効率的です。少なくとも1つの冗長コピーコンストラクター呼び出しが必要です。 pop()が効率的かつ正確な方法で値を返すことは不可能であるため、値をまったく返さない方が賢明ですそして、クライアントにtop()を使用してスタックの最上位の値を検査するように要求します。
SGIはさらにpop()を指定します:
前提条件:empty()はfalseです。事後条件:size()は1ずつ減少します。
Top()の動作に関して、SGIはこれを指定します:
前提条件:empty()はfalseです。
実行するコードはどの環境タイプですか?多くの場合、自分のやり方で物事を打ち出すよりも、既存の行動のパラダイムに一致させる方がはるかに優れています。
空の抽象リストから要素を要求すると、例外がスローされますか?その場合は、フルスタック以外のスタックをポップオフして例外をスローすることをお勧めします。
動作を定義するのが簡単な場合、未定義の動作は悪い選択です。
ほとんどのコードがreturnステートメントを介してアイテムを返す場合、コントロールを返すこと(それが機能したかどうかはわかりません)は悪い設計です。ほとんどのコードがパラメーターリストを介してアイテムを返す場合、同様のコレクションに対する他の呼び出しが同様に行うのであれば、returnステートメントを介してコントロールを返すことは適切な設計です。
空の要素はあまり意味がなく、魔法の値になります。たとえば、リストを作成して5つの空の要素をプッシュした場合、空の要素が含まれていないリストと同じですか?空の要素が1つあるリストと同じですか?いくつかの要素と空の要素を含むリストと同じですか?空のリストが「特別な」オブジェクトであることは1つのことですが、空の要素には要素の動作が実際には含まれておらず、リストの動作が含まれているため、問題があります。適切なオブジェクト指向には、動作の内容が、それが説明するのと同じオブジェクトにカプセル化されています。
空の要素は番兵と同じではないことに注意してください。センチネルは、コレクションに含まれる実装の詳細です(理想的には、外部に公開しないでください)。 「空の要素を返す」を読んだとき、それを使用するには、スタックの実装について熟知している必要があると思います。クラス間の親密さが多すぎると、密結合と呼ばれ、コードの変更/修正/変更がさらに困難になる可能性があります。
自分のやり方で三振をする場合は、最小限、コードの側面全体を同じように動作させる必要があります。メンテを読みやすくします。
pop
メソッドとtrypop
メソッドの両方を使用することをお勧めします。 pop
は単にtrypop
を呼び出し、失敗した場合は例外をスローします。私の推論は、スタックのいくつかの使用法では、スタックが空のときにポップしようとすると、発生してはならないプログラムロジックエラーを示しているということです-不均衡なプッシュ/ポップ、またはリソースの枯渇による以前のプッシュの失敗の誤った処理。他の用途では、ポップに失敗するということは、入力の最後にいることを意味します。例外のあるプログラミングモデルを使用する場合、これらの用途を区別することで、空のスタックのチェックを実行して例外をスローすることで呼び出し元が煩雑になるのを防ぐことができます。
オプションタイプを使用します。おそらく Boost.Optional です。これは、オプションの戻り値のサポートを提供するように特別に設計されています。これは、ここで本当に必要なものです。