当实现运营商[]我如何应包括边界检查?(When implementing operator[] h

2019-09-16 16:38发布

首先,我对很长的达这样一个简单的问题道歉。

我在执行其用作空间填充曲线或表示笛卡尔坐标该索引对应于所述n元组上的非常长的1名维的索引的类。

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

(所述阵列的长度逐指向是尺寸

反正在操作执行[]我想知道什么是最好的方法来实现的边界检查。 我想避免如果在所有可能抛出异常,并且值的完整范围为阵列中的每个号码可用这样一个特殊的值,以在一个出界错误的情况下返回是不可能的任一;

我在想这样的事情,虽然在类定义来实现:

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

这就让我们永远不会离开数组的边界,如果有据可查的,我认为这将是罚款; 不过,我是空的这个特别的实施。

这看起来可以接受别人呢? 是否有这样做边界检查,同​​时仍然满足我的限制任何其他方式?

编辑:之类的东西希尔伯特曲线等计算是非常混乱的,凌乱的,以至于我不不想在这样的STL库的附加接口。

此外,因为我将不得不转换成千上万的这些多维数据库中查询我不想混了STL函数调用的额外费用每一次,如果可能的话。

我比较喜欢断言的想法; 但是,如果我没有记错,在发行休息建立不是吗?

我想我可以使用的例外,这似乎是大家都在全力支持,但我使用Qt库和避免那些出于性能和便携性的例外,我希望这样做。

Answer 1:

最简单的办法是做,因为C ++本身一样。 这限制了你的用户将体验到惊喜的量。

C ++本身是相当一致的。 无论是内置的[]上的指针和std::vector::operator[]如果您使用绑定外的数组索引具有不确定的行为。 如果你想要的边界检查,是明确的和使用std::vector::at

因此,如果你为你的类做同样的,你可以记录外的约束行为,“标准”。



Answer 2:

反正在操作执行[]我想知道什么是最好的方法来实现的边界检查。 我想避免如果在所有可能抛出异常,并且值的完整范围为阵列中的每个号码可用这样一个特殊的值,以在一个出界错误的情况下返回是不可能的任一;

然后,剩下的选项:

  • 灵活的设计。 你做了什么。 “修复”无效的输入,因此,它试图做一些有意义。 优点:功能不会崩溃。 缺点:谁访问越界元素的懵懵懂懂呼叫者会得到一个谎言结果。 想象一下,在1层至10 10楼的建设:

:“谁住在3楼?”

我说:“玛丽”。

:“谁住在9楼?”

我说:“乔”。

:“家住1,203rd楼?”

我:(等待... 1203%10 = 3 ...)> “玛丽”。

:“哇,玛丽必须享受美景那里于是,她拥有两个公寓,然后?”

  • 布尔输出参数指示成功或失败。 此选项通常不是十分有用的代码结束。 很多用户会忽略返回代码。 你还是留下你在其他的返回值返回的内容。

  • 契约式设计。 断言呼叫者范围内。 (对于C ++一个切实可行的办法,见异常或错误?米罗萨梅克或由佩德罗·格雷罗在C ++合同设计简洁支持 。)

  • 返回System.Nullable<quint16> 哎呀,等一下,这不是C#。 嗯,你可以返回一个指向一个quint16。 这当然有很多含义,我将不在此讨论并可能使该选项不可用的。

我最喜欢的选择是:

  • 对于公开发行库的公共接口:输入将被选中并且会抛出异常。 你排除了此选项,所以它不是一个选择。 它仍然是的一个公开发布库的接口选择。
  • 对于内部代码:契约式设计。


Answer 3:

对我来说,这个方案是不可接受的,因为你可以躲在一个很难找到的bug。 抛出越界异常是去,或者至少把一个断言的功能的方式。



Answer 4:

如果你需要的是某种点的“通知”阵列,那么您的解决方案是确定的。 但是,对我来说,它看起来就像躲在一些“安全”的逻辑索引运算符的missusage,这样我就能对你提出的解决方案。

如果不希望允许指数溢出,那么你可以检查并抛出一个异常。

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 ::矢量<quint16>。 它已经有一个基于索引的访问,你可以使用:

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


Answer 5:

有运营商[],从来没有失败听起来不错,但以后可能会在隐藏的错误,如果调用函数使用非法偏移,发现从缓冲区的开始值,然后转到仿佛就是一个有效的值。



Answer 6:

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

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

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



Answer 7:

如果我是你,我会效法由STL设置。

在这种情况下std::vector用品两种方法: at其界限检查和operator[]这是不。 这允许客户端与版本使用的决定。 我绝对不会使用% size()因为这只是隐藏的bug。 然而边界检查将增加大量的开销过大集合迭代时,这就是为什么它应该是可选的。 虽然我与其他海报同意,断言是一个非常好的主意,因为这只会导致性能损失在调试版本。

你也应该考虑返回引用和供应常量,而不是常量的版本。 下面是函数声明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我试图判断或评价它,找到我喜欢的比特和厌恶。



Answer 8:

由于C#的功能在丹尼尔Daranas的职位,我设法找出一个可行的解决方案的评论。 正如我在我的问题说我使用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不需要在被链接。



Answer 9:

也许你可以添加一个例外“出界”的[]操作符(或至少一个assert)。

这应该抓住任何问题,特别是在调试。



Answer 10:

除非我大大误解的东西,

return point[ index % dimensions ];

没有边界检查的。 它是从线的完全不同的部分,这将使它更难被发现的bug返回一个真正的价值。

我要么:

  1. 抛出一个异常或断言(虽然你说你不想这样做)
  2. 简单地提领点过去“自然”的方式排列(即只跳过任何内部检查)。 在%的好处是,他们更可能(虽然不确定是未定义)获得“怪异”的价值观和/或访问冲突

最后,调用者侵犯了您的前置条件,你可以做任何你请。 但我认为这些是最合理的选择。

还考虑什么克特林说如何整合内置的STL集合如果这是合理的。



Answer 11:

如果你提供进入一个椭圆形的点您的解决方案将是很好。 但它会导致非常讨厌的错误,如果你使用它的任意几何函数,因为你故意提供虚假的价值。



Answer 12:

模运算符工作得非常好为数组索引-它也实现负索引(即, point[-3] = point[dimensions - 3] 这是很容易的工作,所以我个人推荐的模运算只要它充分证明。



Answer 13:

另一种选择是让来电者选择了界外政策。 考虑:

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


文章来源: When implementing operator[] how should I include bounds checking?