实现operator []时,如何包含边界检查?

时间:2009-06-22 08:17:30

标签: c++ operator-overloading

首先,我为这么简单的问题长期致以道歉。

我正在实现一个类,它在空间填充曲线上作为非常长的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 指向的数组长度为维度

无论如何在operator []的实现中,我想知道实现边界检查的最佳方法是什么。我想尽可能避免抛出异常,并且数组中的每个数字都可以使用全范围的值,因此在出现越界错误时也不能返回特殊值;

虽然在类定义中实现了,但我正在考虑这样的事情:

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

这使得我们永远不会离开数组的界限,如果有详细记录,我认为它会没事;尽管如此,我对这个特定的实施方式很有兴趣。

这对其他人来说是否可以接受? 在满足我的约束条件的同时还有其他方法来进行边界检查吗?

编辑: Hilbert曲线等事物的计算非常混乱,非常混乱,我不希望stl库的附加接口在路上。

另外,因为每次查询多维数据库时我都必须转换成千上万的这些数据,所以如果可能的话,我不希望混合中的stl函数调用的额外成本。

我更喜欢断言的想法;但是,如果我没记错的话,发布版本中断了吗?

我想我可以使用异常,这似乎是每个人都支持的异常,但我使用的是Qt库,避免了性能和可移植性的异常,我希望也能这样做。

12 个答案:

答案 0 :(得分:12)

最简单的解决方案就是像C ++本身那样做。这会限制用户体验的惊喜数量。

C ++本身相当一致。如果使用超出范围的数组索引,则内置的[]指针和std::vector::operator[]都有未定义的行为。如果您想要边界检查,请明确并使用std::vector::at

因此,如果您为您的班级做同样的事情,您可以将越界行为记录为“标准”。

答案 1 :(得分:9)

  

无论如何执行   operator []我想知道是什么   实现边界检查的最佳方法   是。我想避免投掷   如果可能的例外,和   可以使用全范围的值   数组中的每个数字都是特殊的   退出的价值   边界错误也是不可能的;

然后剩下的选项是:

  • 设计灵活。你做了什么。 “修复”无效输入,以便它尝试做一些有意义的事情。优点:功能不会崩溃。缺点:访问越界元素的无能值呼叫者将获得谎言<​​/ strong>。想象一下10层楼的建筑,楼层1到10:
  

你:“谁住在三楼?”

     

我:“玛丽”。

     

你:“谁住在9楼?”

     

我:“乔”。

     

你:“谁住在1,203楼?”

     

我:(等等...... 1,203%10 = 3 ...)   &GT; “玛丽”

     

你:“哇,玛丽必须从up there欣赏美景。那么她拥有两套公寓呢?”

  • bool输出参数表示成功或失败。此选项通常以不太可用的代码结束。许多用户将忽略返回码。你仍然留下你在其他回报值中返回的内容。

  • 按合同设计。断言调用方在边界内。 (有关C ++中的实用方法,请参阅An exception or a bug? by Miro SamekSimple Support for Design by Contract in C++ by Pedro Guerreiro。)

  • 返回System.Nullable<quint16> 。糟糕,等等,这不是C#。好吧,你可以返回一个指向quint16的指针。这当然有很多含义,我不会在这里讨论,这可能使这个选项无法使用。

我最喜欢的选择是:

  • 对于公开发布的库的公共接口:将检查输入并引发异常。您排除了此选项,因此它不适合您。对于公开发布的库的界面,它仍然是我的选择。
  • 内部代码:按合同设计。

答案 2 :(得分:7)

对我来说,这个解决方案是不可接受的,因为你可以隐藏一个很难找到的bug。 抛出超出范围的异常是要走的路,或者至少在函数中放置一个断言。

答案 3 :(得分: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 ];
}

但是,我建议使用std :: vector&lt;而不是使用point和dimension成员。 quint16&GT;用于点存储。它已经具有您可以使用的基于索引的访问权限:

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

答案 4 :(得分:2)

让一个永不失败的运算符[]听起来不错,但是稍后可能会隐藏错误,如果调用函数使用非法偏移,则从缓冲区的开头找到一个值,并且好像这是一个有效值。

答案 5 :(得分:1)

实现边界检查的最佳方法是添加断言。

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

如果您的代码已经依赖于Boost库,则可能需要使用BOOST_ASSERT

答案 6 :(得分: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;
}

或者我可以使用QPair,它具有完全相同的功能,并且可以使STL不需要链接。

答案 7 :(得分:1)

如果我是你,我会按照stl。

设置的例子

在这种情况下,std::vector提供了两种方法:at边界检查,operator[]不是。% size()。这允许客户端决定使用版本。我肯定不会使用reference at(size_type _Pos); const_reference at(size_type _Pos) const; reference operator[](size_type _Pos); const_reference operator[](size_type _Pos) const; ,因为这只是隐藏了错误。但是,在迭代大型集合时,边界检查会增加很多开销,这就是为什么它应该是可选的。虽然我同意其他海报认为断言是一个非常好的主意,因为这只会导致调试版本中的性能损失。

您还应该考虑返回引用并提供const而不是const版本。以下是std::vector的函数声明:

{{1}}

作为一个很好的经验法则,如果我不确定如何指定API,我会查找其他人如何指定类似API的示例。此外,当我使用API​​时,我会尝试判断或评价它,找到我喜欢和不喜欢的位。

答案 8 :(得分:0)

您可以在[]运算符(或至少是一个断言)中添加“越界”异常。

这应该可以解决任何问题,特别是在调试时。

答案 9 :(得分:0)

除非我对某些事情产生了极大的误解,否则

return point[ index % dimensions ];

根本没有检查边界。它从行的完全不同的部分返回一个真正的值,这将使得检测错误变得更加困难。

我要么:

  1. 抛出异常或断言(虽然你说你不想这样做)
  2. 只需取消引用以“自然”方式指向数组(即跳过任何内部检查)。优于%的优势在于它们更有可能(虽然未定义未定义)以获得“奇怪”值和/或访问冲突
  3. 最后,来电者违反了您的前提条件,您可以随意做任何事情。但我认为这些是最合理的选择。

    如果合理的话,还要考虑Cătălin关于合并内置STL集合的内容。

答案 10 :(得分:0)

如果您提供对椭圆形状的点的访问,那么您的解决方案会很好。但如果你将它用于任意几何函数,它将导致非常讨厌的错误,因为你故意提供错误的值。

答案 11 :(得分:0)

模运算符对数组索引的效果非常好 - 它还实现了负索引(即point[-3] = point[dimensions - 3])。这很容易使用,所以我个人推荐模数运算符,只要它有详细记录。