web-dev-qa-db-ja.com

不変のC ++クラス設計

私はクラスの不変条件を適用するためにどこまで実行するのかについての本当に基本的な質問について考えてきました。多分それは言葉遣いが悪いので、例として、限られた色のパレットを格納するクラスを書きたいとしましょう。クラスのコンストラクターはパレットのサイズを取る必要があります。つまり、必要なだけ色をパレットに追加できるという考えですが、FIFOポリシーでサイズを制限します。したがって、このサイズを1〜32の範囲に任意に制限したいとします(「32色を超える色は必要ありません」)。

始めましょう...

class palette
{
public:
   palette(unsigned int max_size) : m_max_size{max_size} { }
private:
   unsigned int m_max_size;
};

ここまでは順調ですね。最大サイズが私の制限された範囲内にあることを確認するために何もしていません。つまり、不変式は構築から直接解除できます。

(明確にする必要があります:このクラスは、一部のパブリックライブラリでの使用を目的としていません。これは、単一のアプリケーションの内部クラスにすぎません)

「何もしない」以外の4つのオプションが思い浮かびます。

  1. クラスにコメントを付けて、呼び出し元にクラスを正しく使用するように依頼します。

    // Please only call with 1 <= max_size <= 32
    
  2. 範囲内であることを表明します。

    assert(max_size >= 1 && max_size <= 32);
    
  3. 範囲外の場合は例外をスローします

    if (max_size < 1 || max_size > 32)
        throw std::invalid_argument();
    
  4. テンプレートに変換してコンパイラーにチェックを依頼する

    template <unsigned int MAX>
    class palette
    {
    public:
        palette() { // no need for the argument any more
            static_assert(MAX >= 1 && MAX <= 32, "oops");
        }
    };
    

オプション#1は、希望的観測のように見えますが、内部クラスの「単なる」には十分でしょうか。オプション#2から#4は、サイズが次第に複雑になり、#4が構文を変更して、クラスが正しく使用されていない場合にコンパイラーがエラーを報告するようになります。

私はこれについてどんな考えでも歓迎します。答えはおそらく「まあそれはすべてに依存します...」で始まることを理解していますが、私はいくつかの一般的なガイドラインまたはいくつかの代替提案を求めています。

7
WalderFrey

アサーションで行きます。これ以上の情報がなければ、おそらくデフォルトでnon-staticassert()になります。

コメントは確かに希望的な考えです。これを無視できるだけでなく、32を制限する必要がないと判断した場合(いつ?)は、非常に簡単に古くなる可能性があります。

これが他のプログラマーが使用するパブリックAPIである場合、exceptionは、より詳細なデバッグ情報を提供する傾向があるため、特に "walderFrey :: palette size must 1以上32以下である必要があります。」そうでなければ、彼らはあなたのソースコードを掘り下げて、そのランダムなアサートが失敗した理由を理解するためだけにあなたのライブラリがどのように機能するかを学ぶ必要があるかもしれません。しかし、これはあなた自身の使用のためだけなので、それほど多くの利点はありません。また、デバッグ以外のビルドではassert()を非表示にすることで(確かに小さい)パフォーマンス上の利点が得られる場合もあります。

exceptionsは、予測できない例外的な状態を示し、繰り返し発生する可能性が低いか、修正できない場合があると主張しましたが、アサーションは、確実に修正できるおよび修正すべきプログラマーエラーを示していると聞いたことがあります。心配する必要のある公開APIがない場合、私は一般にこのガイドラインに同意します。

テンプレートソリューションは素晴らしいですが、それはより大きな問題を引き起こします:サイズ1のパレットとサイズ32のパレットは同じタイプと見なされるべきですか?一方を他方に割り当てることは意味がありますか?それが意味をなさない場合、それはこのクラスをテンプレートとして実装することの追加の利点です。ただし、static_assert()assert()よりもわずかに優れているため、このクラスをテンプレートにするかどうかを決定すべきではありません。通常のassert()は間違いなく「十分」です。通常、テンプレートを含む一般的なコードはより複雑であるため、複雑さが正当化されると感じるまでは「通常の」クラスをデフォルトとします。したがって、テンプレートの使用が正当化されていると感じない限り、assert()をお勧めしますその他の理由。

4
Ixrec

予見可能な将来のために維持する用意があるという最も強力な保証を使用してください。

方法4(テンプレート/静的アサート)は、コンパイルされたプログラムで不変条件が常にtrueであることを保証します。ただし、これは、入力がコンパイル時に認識されている必要があることを意味しますが、常に可能であるとは限りません。

方法2(アサーション)と3(例外)は実行時に失敗する可能性があるため、方法4よりも弱い保証が提供されます。そして、不変条件を維持できないことの起こりうる影響。詳細な扱いについては、 John Lakos-防御的プログラミングの完了 を参照してください。

これをロジック(またはプログラマー)エラーと見なす場合は、アサーションを使用します。不変条件を壊すことで損傷(ハードウェアの損傷、データの破壊など)が発生する可能性は低く、エラーを変更/再コンパイルせずにエラーを防ぐ方法はありません。コード。アサーションは通常、リリースビルドではなく、デバッグビルドでのみチェックされます。

エラーが「環境状況」(ネットワーク障害など)によって引き起こされる可能性がある場合、または無効な入力が発生した場合、またはエラーにもかかわらず続行しても損傷が発生する場合は、例外をスローします。もちろん、例外は呼び出しコードのどこかで処理する必要があります。つまり、例外をスローすることで、クラスのセマンティクスがより複雑になるということです。

方法1(コメント)はコードドキュメントであり、これは常に他の方法のいずれかに加えて行う必要があります。このクラスを使用しているユーザーに、ソースコードを見てその使用方法を理解させる必要はありません。これはドキュメントから明らかです。例外をスローしてエラーを処理する場合は、それも文書化する必要があります。

1
D Drmmr