2009-06-22 6 views
3

まず最初に、このような単純な質問までの長いリードについてお詫び申し上げます。演算子[]を実装するとき、どのように境界チェックを含めるべきですか?

私は、空間充填曲線上の非常に長い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; 
}; 

(配列の長さは、が寸法ある点によって指さ)

[] Iは境界チェックを達成するための最良の方法であると思いまし。可能であれば例外を投げるのを避けたいですし、配列の各数値に対して完全な値の範囲を使用できるので、範囲外のエラーの場合に返す特別な値は不可能です。

私は、クラス定義で実装され、このかかわらような何かを考えていた:

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

我々は、配列の境界を離れることはないと十分に文書場合、私はそれは大丈夫だと思うようにするためには、それを作ります。それにもかかわらず、私はこの特定の実装が気にしません。

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

編集: ヒルベルト曲線などの計算は非常に扱いにくいですが、私は途中でstlライブラリの追加インターフェイスを必要としないほど面倒です。

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

私は、主張の考え方にかなり似ています。しかし、リリースビルドの中断が正しくないことを私が覚えていればそれはありませんか?

誰もが根っこにしているような例外を使うことができると思いますが、Qtライブラリを使用していて、パフォーマンスと移植性の両方で例外を避けています。

答えて

12

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

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

したがって、クラスに対して同じ処理を行う場合は、範囲外の動作を「標準」として文書化できます。

+0

興味深い点。 –

+0

このオプションはわかりやすいようですが、多次元データベースのコードでは境界をチェックするのに非常に注意していますので、おそらくこれで逃げることができます(特に、データベースコードの外に誰もカーブポイントタイプ)。 アイデアをありがとう。今、私はこの答えと私がこの手紙の直後に書いた答えの間に私の頭の中に投げつけています。 –

+0

これは標準的な動作ですが、実装が[]オーバーロードを含む他の場所で(オプションの)境界チェックを提供することを停止するものは何もありません。そして、これらがあなたのプラットフォームで利用可能な場合は、スイッチを切るよりもスイッチをオンにするほうが正気であるように見えます。プロファイリングが本当に価値があることがわかっている場合は、バイパスしてください。だから私はどこにでもあなたがそうする機会を持っていることを確認することをお勧めします。 –

3

あなたが必要とするのは何らかの「円形の」点列ですが、解決策は問題ありません。しかし、私にとっては、 "安全な"ロジックの背後にある演算子の索引付けを誤って隠すように見えるので、私はあなたの提案された解決策に反対します。

インデックスのオーバーフローを許可しない場合は、例外をチェックしてスローすることができます。あなたが少ないオーバーヘッドを持っているしたい場合は、デバッグ時間のアサーションを使用して、例外を避けることができ

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 ]; 
} 

しかし、私は代わりに、以下のことを示唆ポイントメンバとディメンションメンバを使用するには、point storage用にstd :: vector < quint16>を使用します。それはすでにあなたが使用することができ、インデックスベースのアクセスがあります。私にとって

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

をあなたはバグを見つけるのは非常に困難を隠すことができますので、この解決策は受け入れられません。 範囲外の例外をスローすることは、少なくとも関数にアサーションを入れる方法です。

+1

もしあれば、ユーザーに選択肢を与えます。 NDEBUGが定義されている場合はチェックを入れず、NDEBUGが定義されている場合はNO_BOUNDS_CHECKINGを定義し、NO_BOUNDS_CHECKINGが定義されているかどうかはチェックしないでください。そうすれば、ユーザーはカスタムビヘイビアを取得する前に "#define NO_BOUNDS_CHECKING"を置くことができます。 – GManNickG

+1

私は自分の質問で言ったように、可能ならば例外を避けたい。私が正しく覚えていれば、デバッグライブラリなしでコンパイルするとブレークします。 –

+1

私は解決策が受け入れられないことに同意しますが、小切手と例外に対するアサートを好む。大規模なコレクションを繰り返し処理するときには、多くのオーバーヘッドが発生し、通常はクライアントによってforループの形で提供されます。 'for(size_t i = 0; i <コレクション。サイズ(); ++ i) ' – iain

0

おそらく、[範囲外]例外を[]演算子(または少なくともアサート)に追加できます。

これは、特にデバッグ時に問題を検出するはずです。

1

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

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

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

+0

でも、OPは明示的に非デバッグビルドでの除外のために 'assert'に関する疑問を述べていました。彼らは、デバッグビルドだけでなく、常に機能するものを望んでいるようです。 '断言'はそうではありません。我々は既に '.at()'ではなく 'operator []'で境界チェックを行うことでSTLの期待を覆しています。境界モードのチェックがビルド・モードに応じて不思議に消えてしまうと悪化します。 –

2

オペレータ[]が決して失敗しないのはうれしいですが、呼び出し元関数が不正なオフセットを使用し、バッファの先頭から値を見つけて、それが有効な値であるかのように処理して、 。 演算子[]私は を境界チェックを達成するための 最善の方法があるかと思ったの実装でとにかく

9

。可能であれば 例外を投げるのを避けたいと考えており、 の配列の各数値に対して の値域を使用できるので、 の値が の範囲外の場合に返される特別な値もありません。

その後、残りのオプションは以下のとおりです。

  • 柔軟な設計。あなたがしたこと。無効な入力を「修正」して意味のある処理を試みます。利点:機能はクラッシュしません。短所:範囲外の要素にアクセスする有線の発信者は、結果として거짓を取得します。 10階1と10階建ての建物を想像してみて:

あなたは:「3階に住んでいる誰ですか?」

Me:"Mary"

あなた:「9階に住んでいる人は?」

Me:"Joe"。

お客様:「1,203階に住んでいる人は?」

私:(... ... 1203パーセント10 = 3を待って) > "メアリー"

あなた:「うわー、メアリーはup thereからすばらしい景色を楽しむ必要があります。彼女は2つのアパートを所有していますか?「

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

  • デザイン。アサート呼び出し側が境界内にあること。(C++で実用的なアプローチについては、An exception or a bug? by Miro Samek又はSimple Support for Design by Contract in C++ by Pedro Guerreiroを参照。)

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

私のお気に入りの選択肢は次のとおり公表ライブラリのパブリックインターフェイスのために

  • :入力がチェックされ、例外がスローされます。あなたはこのオプションを除外したので、それはあなたのための選択肢ではありません。公式にリリースされたライブラリのインターフェースのために、まだ私のの選択です。
  • 内部コードの場合:契約によって設計されています。私は劇的に何かを誤解していない限り
+0

"契約による設計"の部分を除いて私はあなたの投稿に同意します。 DbCは言語で実装された機能としては優れています(強力な型付けをしたコンパイルされた言語と同じように、正しく使用するといくつかのセーフガードが作成されます)。しかし、ほとんどの言語はそれを実装していないので(それは残念ですが)、私は通常防御的なアプローチをとる傾向があります。「すべての入力は潜在的に間違っています。もちろん、あなた自身のコードとそれを適切に使用する方法を知っています。しかし、同じプロジェクトに取り組んでいる人、またはあなたを置き換える人はおそらくそうではありません。 – Ksempac

+0

@Ksempac:DbC防御的なアプローチではなく、攻撃的なアプローチを取っています。私の関数_requires_は、境界内でパラメータを送信します。この前提条件に違反したければ、楽しみにしてください。リリースモードでは、何か起こることについて私は責任を負いません。デバッグモードでは、前提条件がチェックされ、呼び出しエラーが検出されます。 –

+1

+1 DbCの場合、アサートはEiffleのサポートやJavaのJMLで提供される機能と同じくらいうまくないものの、C++のDbCに使用できます – iain

0

return point[ index % dimensions ]; 

は全く境界チェックではありません。それは、バグを検出することを非常に困難にするラインの全く異なる部分から本当の価値を戻しています。

私はどちらかになります。

  1. (あなたがそうしたくないと述べたが)「自然な」方法(すなわち、アレイを過ぎて単にポイントを逆参照
  2. 例外またはアサーションを投げます内部チェックをスキップするだけです)。 %以上のメリットは、「奇妙な」値やアクセス違反の可能性が高い(未定義ですが)可能性が高いことです。

最後に、発信者があなたの前提条件に違反しています。あなたは何でもできます。しかし、私はこれらが最も合理的な選択肢だと思う。

また、Cătălinは、妥当であれば組み込みのSTLコレクションを組み込んでいると述べました。

0

楕円形の点へのアクセスを提供していれば、あなたのソリューションはいいでしょう。しかし、あなたが意図的に偽の値を提供するので、任意の幾何学的関数に使用すると、非常に厄介なバグにつながります。

+0

これらの点は、任意の高次元空間の点です。次に、curveCalculate関数クラスを使用して、これらのデカルト座標を、それらの座標を表す与えられた空間塗りつぶし曲線に沿って1次元インデックスに変換します。 –

+0

より正確には、クラスには1つのポイントが含まれています。申し訳ありませんが、私はそれを明確にしませんでした。 –

1

Daniel Daranasの記事のC#のコメントのおかげで、私は可能な解決策を見つけ出すことができました。私の質問に述べたように、私はQtライブラリを使用しています。私は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; 
} 

または私はまったく同じ機能を持ち、STLの中でリンクされる必要がないようにそれになるだろうQPAIRを、使用することができます。

1

私があなただったら、私はで設定された例をたどりますstl。

この場合、std::vectorは、チェックされた範囲がatで、operator[]ではありません。これにより、クライアントは使用するバージョンを決定することができます。私は間違いなく、% size()を使用しません。これはバグを隠すだけです。しかし、大規模なコレクションを繰り返し処理する場合には、境界チェックでオーバーヘッドが増えるため、オプションである必要があります。私は他のポスターと同意しますが、アサートは非常に良いアイデアです。これは、デバッグビルドでのみパフォーマンスが低下するためです。

また、参照を返し、constバージョンではなくconstバージョンを提供することを検討する必要があります。ここでstd::vectorのための関数の宣言は、次のとおりです。

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を使用するとき、私はそれを判断したり評価したり、好きなビットを見つけたり、嫌いにしたりしようとします。

0

モジュロ演算子は、配列インデックスに対して驚くほどうまく機能します。負のインデックスも実装しています(つまり、point[-3] = point[dimensions - 3])。これは簡単に処理できるので、モジュロ演算子がよく書かれている限り、それを個人的にお勧めします。

+0

モジュロ演算子とは何ですか?私は残りの演算子 '%'に精通していますが、それは厄介なように負の数で動作します。 – supercat

0

もう1つの方法は、発信者に範囲外ポリシーを選択させることです。以下を検討してください。

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; 
    } 
}; 
関連する問題