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

发布于 2024-07-25 22:47:47 字数 1653 浏览 4 评论 0原文

首先,我为这么长时间提出这样一个简单的问题表示歉意。

我正在实现一个类,它充当空间填充曲线上非常长的一维索引或表示索引对应的笛卡尔坐标的 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;
};

指向的数组的长度是维度

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

我正在考虑类似的事情,尽管在类定义中实现了:

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

这使得我们永远不会离开数组的边界,如果有充分的记录,我认为这会很好; 尽管如此,我对这个特定的实现还是很了解的。

这看起来别人可以接受吗? 有没有其他方法可以在满足我的约束的同时进行边界检查?

编辑: 希尔伯特曲线等的计算非常混乱,混乱到我不希望 stl 库的附加接口受到干扰。

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

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

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

First of all I apologize for the long lead up to such a simplistic question.

I am implementing a class which serves as a very long 1 dimensional index on a space filling curve or the n-tuple representing the Cartesian coordinate that index corresponds to.

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

(The length of the array pointed to by point is dimensions)

Anyways in the implementation of operator[] I was wondering what the best method to achieve bounds checking is. I want to avoid throwing exceptions if at all possible, and the full range of values is usable for each number in the array so a special value to return in case of an out of bounds error is not possible either;

I was thinking of something like this though implemented in the class definition:

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

This makes it so that we never leave the bounds of the array and if well documented I think it would be fine; nevertheless, I am leary of this particular implementation.

Does this look acceptable to others?
Is there any other way of doing bounds checking while still satisfying my constraints?

Edit:
Calculation of things like Hilbert curves etc are highly messy, messy enough that I do not not want the additional interface for the stl libraries in the way.

Additionally because I will have to convert many thousands of these every time the multidimensional database is queried I do not want the additional cost of the stl function calls in the mix, if at all possible.

I rather like the idea of the assert; but, if I remember correctly that breaks in release builds does it not?

I suppose I can use exceptions, that seems to be what everyone is rooting for, but I am using the Qt libraries and those avoid exceptions for both performance and portability and I was hoping to do the same.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

不及他 2024-08-01 22:47:48

拥有一个永远不会失败的operator[]听起来不错,但如果调用函数使用非法偏移量,从缓冲区的开头找到一个值,然后继续处理,就好像这是一个有效值一样,以后可能会隐藏错误。

Having an operator[] that never fails sounds nice, but may hide bugs later on, if a calling function uses an illegal offset, finds a value from the beginning of the buffer, and proceeds as if that is a valid value.

狼性发作 2024-08-01 22:47:48

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

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

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

The best method to achieve bounds checking would be to add an assert.

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

If your code already depends on Boost libraries, you might want to use BOOST_ASSERT instead.

彡翼 2024-08-01 22:47:48

如果我是你,我会遵循 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 时,我会尝试对其进行判断或评级,找出我喜欢和不喜欢的部分。

If I were you I would follow the example set by the stl.

In this case std::vector supplies two methods: at which is bounds checked and operator[] which is not. This allows the client to decide with version to use. I would definitely not use the % size(), as this just hides the bug. However bounds checking will add a lot of overhead for when iterating over a large collection, this is why it should be optional. Although I agree with other posters that the assert is a very good idea as this will only cause a performance hit in debug builds.

You should also consider returning references and supplying const and not const versions. Here are the function declarations for 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;

As a good rule of thumb if I am not sure how to specify an API I look for examples of how others specify similar APIs. Also when ever I use an API I try to judge or rate it, find the bits I like and dislike.

倚栏听风 2024-08-01 22:47:48

感谢 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。

Thanks to the comment on the C# feature in the post of Daniel Daranas I have managed to figure out a possible solution. As I stated in my question I am using the Qt libraries. There for I can use QVariant. QVariant can be set to an invalid state that can be checked by the function receiving it. So the code would become something like:

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

    return temp;
}

Of course this has the potential of inserting a bit of gnarly overhead into the function so another possibility is to use a pair template.

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

Or I could use a QPair, which has exactly the same functionality and would make it so that the STL need not be linked in.

内心荒芜 2024-08-01 22:47:48

另一种选择是让调用者选择越界策略。 考虑:

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

Another option is to let the caller choose the out-of-bounds policy. Consider:

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

Then you could define several policies that the caller may choose. For example:

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;
    }
};
混浊又暗下来 2024-08-01 22:47:48

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

这应该可以捕获任何问题,尤其是在调试时。

You could perhaps add an "out of bounds" exception to the [] operator (or at least an assert).

This should catch any problems, especially when debugging.

孤独患者 2024-08-01 22:47:48

除非我严重误解了某些东西,否则

return point[ index % dimensions ];

根本不是边界检查。 它从线路的完全不同部分返回真实值,这将使检测错误变得更加困难。

我会:

  1. 抛出异常或断言(尽管您说过您不想这样做)
  2. 以“自然”方式简单地取消引用指向数组(即跳过任何内部检查)。 相对于%的优点是它们更有可能(尽管未定义是未定义的)获得“奇怪”的值和/或访问冲突

最后,调用者违反了你的先决条件,你可以做任何你想做的事。 但我认为这些都是最合理的选择。

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

Unless I'm drastically misunderstanding something,

return point[ index % dimensions ];

is not bounds checking at all. It's returning a real value from a totally different part of the line, which will make it much harder to detect bugs.

I would either:

  1. Throw an exception or an assertion (though you said you don't want to do so)
  2. Simply dereference point past the array in a "natural" way (i.e. just skip any internal checking). The advantage over the % is that they're more likely (though undefined is undefined) to get "weird" values and/or an access violation

In the end, the caller is violating your pre-conditions, and you can do whatever you please. But I think these are the most reasonable options.

Also consider what Cătălin said about incorporating built-in STL collections if that's reasonable.

很快妥协 2024-08-01 22:47:48

如果您提供对椭圆形点的访问,您的解决方案会很好。 但如果您将其用于任意几何函数,则会导致非常严重的错误,因为您故意提供了错误值。

Your solution would be nice if you were providing access to the points of an elliptic shape. But it will lead to very nasty bugs if you use it for arbitrary geometric functions, because you knowingly provide a false value.

夜雨飘雪 2024-08-01 22:47:48

模运算符对于数组索引的效果出人意料地好——它还实现了负索引(即point[-3] = point[dimensions - 3])。 这很容易使用,因此我个人推荐使用模运算符,只要它有详细的文档记录即可。

The modulo operator works surprisingly well for array indices -- it also implements negative indices (ie. point[-3] = point[dimensions - 3]). This is easy to work with, so I'd personally recommend the modulo operator as long as it's well-documented.

天涯沦落人 2024-08-01 22:47:47

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

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

因此,如果您对类执行相同的操作,则可以将越界行为记录为“标准”。

The easiest solution is to do as C++ itself does. This limits the amount of surprises that your users will experience.

C++ itself is fairly consistent. Both the built-in [] on pointers and std::vector::operator[] have undefined behavior if you use an out-of-bound array index. If you want bounds checking, be explicit and use std::vector::at

Hence, if you do the same for your class, you can document the out-of-bound behavior as "standard".

那支青花 2024-08-01 22:47:47

无论如何在实施中
运算符[] 我想知道什么
实现边界检查的最佳方法
是。 我想避免抛出
如果可能的话,例外,并且
全范围的值可用于
数组中的每个数字都是特殊的
超出时返回的值
边界错误也是不可能的;

那么剩下的选项是:

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

你:“谁住在三楼?”

我:“玛丽”。

你:“谁住在九楼?”

我:“乔”。

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

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

你:“哇,玛丽一定能欣赏到 那么她拥有两套公寓吗?”


  • bool 输出参数表示成功或失败。 此选项通常会生成不太可用的代码。 许多用户会忽略返回码。 您仍然保留在另一个返回值中返回的内容。

  • 按合同设计。断言调用者在范围内。 (有关 C++ 中的实用方法,请参阅 Miro Samek 的异常还是错误? 或 Pedro Guerreiro 的 C++ 合约设计简单支持。)

  • 返回一个System.Nullable。 哎呀,等等,这不是 C#。 好吧,您可以返回一个指向 quint16 的指针。 这当然有很多含义,我不会在这里讨论,并且可能使该选项不可用。

我最喜欢的选择是:

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

Anyways in the implementation of
operator[] I was wondering what the
best method to achieve bounds checking
is. I want to avoid throwing
exceptions if at all possible, and the
full range of values is usable for
each number in the array so a special
value to return in case of an out of
bounds error is not possible either;

Then the remaining options are:

  • Flexible design. What you did. "Fix" the invalid input so that it tries to do something which makes sense. Advantage: Function won't crash. Disadvantage: Clueless callers who access an out of bounds element will get a lie as a result. Imagine a 10-floor building with floors 1 to 10:

You: "Who lives in the 3rd floor?"

Me: "Mary".

You: "Who lives in the 9th floor?"

Me: "Joe".

You: "Who lives in the 1,203rd floor?"

Me: (Wait... 1,203 % 10 = 3...)
> "Mary".

You: "Wow, Mary must enjoy great views from up there. So she owns two apartments then?"

  • A bool output parameter indicates success or failure. This option usually ends up in not very usable code. Many users will ignore the return code. You are still left with what you return in the other return value.

  • Design by Contract. Assert that the caller is within bounds. (For a practical approach in C++, see An exception or a bug? by Miro Samek or Simple Support for Design by Contract in C++ by Pedro Guerreiro.)

  • Return a System.Nullable<quint16>. Oops, wait, this is not C#. Well, you could return a pointer to a quint16. This of course has lots of implications which I shall not discuss here and which probably make this option not usable.

My favorite choices are:

  • For the public interface of a publicly released library: Input will be checked and an exception will be thrown. You ruled out this option, so it is not an option for you. It is still my choice for the interface of a publicly released library.
  • For internal code: Design by contract.
一枫情书 2024-08-01 22:47:47

对我来说,这个解决方案是不可接受的,因为你可能隐藏了一个很难发现的错误。
抛出超出范围的异常是正确的方法,或者至少在函数中放置一个断言。

For me, this solution is unacceptable because you can be hiding a very hard to find bug.
Throwing an out of range exception is the way to go, or at least put an assertion in the function.

只涨不跌 2024-08-01 22:47:47

如果您需要的是某种“圆形”点数组,那么您的解决方案就可以。 然而,对我来说,它看起来就像将索引运算符的误用隐藏在一些“安全”逻辑后面,所以我反对您提出的解决方案。

如果不想允许索引溢出,那么您可以检查并抛出异常。

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< quint16> 用于点存储。 它已经具有您可以使用的基于索引的访问:

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

If what you need is some sort of "circular" array of points, then your solution is ok. However, to me, it looks just like hiding missusage of indexing operator behind some "safe" logic, so I would be against your proposed solution.

If don't want to allow index overflow, then you could check and throw an exception.

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

If you want to have less overhead, you could avoid exception, by using debug time assertions (assume that the provided index is always valid):

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

However, I suggest that, instead of using point and dimension members, use a std::vector< quint16> for point storage. It already has an index based access that you could use:

quint16 curvePoint::operator[](size_t index)
{
    // points is declared as std::vector< quint16> points;
    return points[ index ];
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文