operator []を実装する場合、境界チェックをどのように含める必要がありますか?

StackOverflow https://stackoverflow.com/questions/1026042

  •  06-07-2019
  •  | 
  •  

質問

まず、このような単純な質問に至るまで長い間おlongび申し上げます。

空間充填曲線上で非常に長い1次元インデックスとして機能するクラス、またはインデックスが対応するデカルト座標を表すnタプルを実装しています。

class curvePoint
{
public:
    friend class curveCalculate;

    //Construction and Destruction
    curvePoint(): point(NULL), dimensions(0) {}
    virtual ~curvePoint(){if(point!=NULL) delete[] point;}

    //Mutators
    void convertToIndex(){ if(isTuple()) calc(this); }
    void convertToTuple(){ if(isIndex()) calc(this); }
    void setTuple(quint16 *tuple, int size);
    void setIndex(quint16 *index, int size);
    void setAlgorithm(curveType alg){algorithm = alg;}

    //Inspectors
    bool isIndex(){return current==Index;}
    bool isTuple(){return current==Tuple;}
    size_t size(){return dimensions;}
    quint16 operator[](size_t index);

    enum curveType{HilbertCurve, ZCurve, GrayCodeCurve};
    enum status{Index, Tuple};

private:
    curveCalculate calc;
    curveType algorithm;
    quint16 *point;
    size_t dimensions;
    status current;
};

point が指す配列の長さは dimensions

とにかくoperator []の実装では、境界チェックを達成するための最良の方法は何だろうと思っていました。可能な限り例外をスローしないようにしたいと思います。また、値の全範囲を配列内の各数値に使用できるため、範囲外エラーの場合に返す特別な値も不可能です。

クラス定義に実装されているものの、このようなことを考えていました:

quint16 curvePoint::operator[](size_t index)
{
    return point[ index % dimensions ];
}

これにより、配列の境界を離れることがなくなり、適切に文書化されていれば問題ないと思います。それにもかかわらず、私はこの特定の実装を心配しています。

これは他の人に受け入れられるように見えますか? 制約を満たしながら境界チェックを行う他の方法はありますか?

編集: ヒルベルト曲線などの計算は非常に面倒で、面倒なので、stlライブラリの追加のインターフェイスが邪魔にならないようにします。

さらに、多次元データベースが照会されるたびに何千ものこれらを変換する必要があるため、可能であれば、ミックス内のstl関数呼び出しの追加コストは必要ありません。

私はむしろアサートのアイデアが好きです。しかし、リリースビルドの中断が正しくないことを正しく覚えている場合はどうなりますか?

例外を使用できると思いますが、それは誰もが応援しているもののようですが、私はQtライブラリを使用しており、パフォーマンスと移植性の両方の例外を回避し、同じことを望んでいました。

役に立ちましたか?

解決

最も簡単な解決策は、C ++自体が行うようにすることです。これにより、ユーザーが経験する驚きの量が制限されます。

C ++自体はかなり一貫しています。範囲外の配列インデックスを使用する場合、ポインターの組み込み[]std::vector::operator[]の両方に未定義の動作があります。境界チェックが必要な場合は、明示的にstd::vector::at

を使用します

したがって、クラスで同じことを行う場合、範囲外の動作を<!> quot; standard <!> quot;として文書化できます。

他のヒント

  

とにかく   operator []私は何を疑問に思っていました   境界チェックを実現する最良の方法   です。投げたくない   可能な限り例外、および   値の全範囲が使用可能です   配列内の各数値は特別なので   の場合に返す値   境界エラーも起こり得ません;

残りのオプションは次のとおりです。

  • 柔軟なデザイン。あなたがしたこと。 <!> quot;修正<!> quot;無効な入力であるため、意味のあることをしようとします。利点:関数はクラッシュしません。欠点:境界外の要素にアクセスする無知な呼び出し元は、結果としてを受け取ります。 1階から10階までの10階建ての建物を想像してください:
  

あなた: <!> quot; 3階に住んでいるのは誰ですか?<!> quot;

     

Me: <!> quot; Mary <!> quot;。

     

あなた: <!> quot; 9階に住んでいるのは誰ですか?<!> quot;

     

Me: <!> quot; Joe <!> quot;。

     

あなた: <!> quot; 1,203階に住んでいるのは誰ですか?<!> quot;

     

私:(待って... 1,203%10 = 3 ...)    <!> gt; <!> quot; Mary <!> quot;

     

あなた: <!> quot;すごい、メアリーはそこまで。彼女は2つのアパートを所有していますか?<!> quot;

  • bool出力パラメーターは、成功または失敗を示します。このオプションは通常、あまり使用できないコードになります。多くのユーザーは戻りコードを無視します。他の戻り値で返すものが残っています。

  • 契約による設計。発信者が範囲内にいることを確認します。 (C ++での実用的なアプローチについては、例外またはバグ?ミロサメクまたは Pedro GuerreiroによるC ++の契約による設計の簡単なサポート

  • System.Nullable<quint16> を返します。おっと、これはC#ではありません。さて、あなたはquint16へのポインタを返すことができます。もちろん、これには多くの意味がありますが、ここでは説明しませんが、おそらくこのオプションは使用できません。

お気に入りの選択肢:

  • 公開されているライブラリのパブリックインターフェイスの場合:入力がチェックされ、例外がスローされます。このオプションは除外されているため、オプションではありません。公にリリースされたライブラリのインターフェースとしては、まだ私の選択です。
  • 内部コードの場合:契約による設計。

私にとって、この解決策は、見つけにくいバグを隠すことができるため、受け入れられません。 範囲外の例外をスローする方法があります。少なくとも関数にアサーションを追加します。

必要なものが<!> quot; circular <!> quot;の場合ポイントの配列、あなたのソリューションは大丈夫です。しかし、私にとっては、インデックス演算子の誤用をいくつかの<!> quot; safe <!> quotの後ろに隠すように見えます。ロジック、私はあなたの提案されたソリューションに反対するでしょう。

インデックスのオーバーフローを許可しない場合は、チェックして例外をスローできます。

quint16 curvePoint::operator[](size_t index)
{
    if( index >= dimensions)
    {
       throw std::overflow_error();
    }
    return point[ index ];
}

オーバーヘッドを減らしたい場合は、デバッグ時のアサーションを使用して例外を回避できます(提供されたインデックスが常に有効であると仮定します):

quint16 curvePoint::operator[](size_t index)
{
    assert( index < dimensions);
    return point[ index ];
}

ただし、ポイントメンバとディメンションメンバを使用する代わりに、std :: vector <!> lt;を使用することをお勧めします。 quint16 <!> gt;ポイントストレージ用。既に使用可能なインデックスベースのアクセスがあります。

quint16 curvePoint::operator[](size_t index)
{
    // points is declared as std::vector< quint16> points;
    return points[ index ];
}

決して失敗しないoperator []を持っているのは良いことですが、呼び出し関数が不正なオフセットを使用し、バッファーの先頭から値を見つけて、それが有効な値であるかのように進む場合、後でバグを隠す可能性があります。

境界チェックを実現する最良の方法は、アサートを追加することです。

quint16 curvePoint::operator[](size_t index)
{
    assert(index < dimensions);
    return point[index];
}

コードがすでにBoostライブラリに依存している場合は、代わりにBOOST_ASSERTを使用できます。

私があなたなら、stlによって設定された例に従います。

この場合、std::vectorは2つのメソッドを提供します。atは境界チェックで、operator[]はチェックされません。これにより、クライアントは使用するバージョンを決定できます。 % size()はバグを隠すだけなので、絶対に使用しません。ただし、境界チェックを行うと、大きなコレクションを反復処理する際のオーバーヘッドが大きくなるため、オプションにする必要があります。他の投稿者には同意しますが、アサートはデバッグビルドでのパフォーマンスヒットのみを引き起こすため、非常に良いアイデアであると思います。

また、参照を返し、constバージョンではなくconstを提供することを検討する必要があります。以下は、 <=> の関数宣言です。 :

reference at(size_type _Pos);
const_reference at(size_type _Pos) const;

reference operator[](size_type _Pos);
const_reference operator[](size_type _Pos) const;

良い経験則として、APIの指定方法がわからない場合は、他の人が同様のAPIを指定する方法の例を探します。また、APIを使用するときは、それを判断または評価し、好きなものと嫌いなものを見つけます。

Daniel Daranasの投稿にあるC#機能に関するコメントのおかげで、解決策を見つけることができました。私の質問で述べたように、私はQtライブラリを使用しています。 QVariantを使用できます。 QVariantを無効な状態に設定すると、QVariantを受信する機能で確認できます。したがって、コードは次のようになります。

QVariant curvePoint::operator[](size_t index){
    QVariant temp;
    if(index > dimensions){
        temp = QVariant(QVariant::Invalid);
    }
    else{
        temp = QVariant(point[index]);
    }

    return temp;
}

もちろん、これは関数にちょっとしたひどいオーバーヘッドを挿入する可能性があるため、別の可能性はペアテンプレートを使用することです。

std::pair<quint16, bool> curvePoint::operator[](size_t index){
    std::pair<quint16, bool> temp;
    if(index > dimensions){
        temp.second = false;
    }
    else{
        temp.second = true;
        temp.first = point[index];
    }
    return temp;
}

またはQPairを使用することもできます。QPairはまったく同じ機能を持ち、STLをリンクする必要がないようにします。

<!> quot; out of bounds <!> quot;を追加できます。 []演算子の例外(または少なくともアサート)。

これは、特にデバッグ時に問題をキャッチするはずです。

何かを大幅に誤解していない限り、

return point[ index % dimensions ];

は境界チェックではありません。行のまったく異なる部分から実際の値を返すため、バグを検出するのがはるかに難しくなります。

次のいずれかです:

  1. 例外またはアサーションをスローします(そうはしたくないと言いましたが)
  2. 単に<!> quot; natural <!> quot;内の配列を越えてポイントを逆参照します。方法(つまり、内部チェックをスキップします)。 %に対する利点は、<!> quot; weird <!> quot;を取得する可能性が高いことです(ただし、undefinedはundefinedです)。値および/またはアクセス違反

最終的に、発信者はあなたの前提条件に違反しており、あなたは好きなことをすることができます。しかし、これらは最も合理的なオプションだと思います。

また、C <!>#259; t <!>#259; linが、ビルトインSTLコレクションを組み込むことについて合理的な場合に言ったことも考慮してください。

楕円形のポイントへのアクセスを提供している場合、あなたのソリューションは素晴らしいでしょう。ただし、任意の幾何学的関数に使用すると、意図的に誤った値を提供するため、非常に厄介なバグにつながります。

モジュロ演算子は、配列インデックスに対して驚くほどうまく機能します-負のインデックスも実装します(つまりpoint[-3] = point[dimensions - 3])。これは扱いやすいので、十分に文書化されている限り、モジュロ演算子を個人的にお勧めします。

別のオプションは、発信者に範囲外ポリシーを選択させることです。考慮:

template <class OutOfBoundsPolicy>
quint16 curvePoint::operator[](size_t index)
{
    index = OutOfBoundsPolicy(index, dimensions);
    return point[index];
}

次に、発信者が選択できるいくつかのポリシーを定義できます。例:

struct NoBoundsCheck {
    size_t operator()(size_t index, size_t /* max */) {
        return index;
    }
};

struct WrapAroundIfOutOfBounds {
    size_t operator()(size_t index, size_t max) {
        return index % max;
    }
};

struct AssertIfOutOfBounds {
    size_t operator()(size_t index, size_t max) {
        assert(index < max);
        return index % max;
    }
};

struct ThrowIfOutOfBounds {
    size_t operator()(size_t index, size_t max) {
        if (index >= max) throw std::domain_error;
        return index;
    }
};

struct ClampIfOutOfBounds {
    size_t operator()(size_t index, size_t max) {
        if (index >= max) index = max - 1;
        return index;
    }
};
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top