使用强类型语言还有多远?

Dan*_*Dan 42 c++ api types strong-typing

假设我正在编写一个API,我的一个函数采用一个代表一个通道的参数,并且只会在值0和15之间.我可以这样写:

void Func(unsigned char channel)
{
    if(channel < 0 || channel > 15)
    { // throw some exception }
    // do something
}
Run Code Online (Sandbox Code Playgroud)

或者我是否利用C++作为强类型语言,并使自己成为一种类型:

class CChannel
{
public:
    CChannel(unsigned char value) : m_Value(value)
    {
        if(channel < 0 || channel > 15)
        { // throw some exception }
    }
    operator unsigned char() { return m_Value; }
private:
    unsigned char m_Value;
}
Run Code Online (Sandbox Code Playgroud)

我的功能现在变为:

void Func(const CChannel &channel)
{
    // No input checking required
    // do something
}
Run Code Online (Sandbox Code Playgroud)

但这总是矫枉过正吗?我喜欢自我记录,并保证它是它所说的,但它是否值得支付这样一个对象的构造和破坏,更不用说所有额外的打字?请让我知道您的意见和备选方案.

GMa*_*ckG 60

如果你想要这种简单的方法可以概括它,那么你就可以更多地使用它,而不是将它定制为特定的东西.那么问题不是"我应该为这个特定的事情做一个全新的课程吗?" 但"我应该使用我的实用程序吗?"; 后者永远是肯定的.公用事业总是有用的.

所以做一些像:

template <typename T>
void check_range(const T& pX, const T& pMin, const T& pMax)
{
    if (pX < pMin || pX > pMax)
        throw std::out_of_range("check_range failed"); // or something else
}
Run Code Online (Sandbox Code Playgroud)

现在你已经有了这个很好的实用程序来检查范围.即使没有通道类型,您的代码也可以通过使用它来变得更干净.你可以走得更远:

template <typename T, T Min, T Max>
class ranged_value
{
public:
    typedef T value_type;

    static const value_type minimum = Min;
    static const value_type maximum = Max;

    ranged_value(const value_type& pValue = value_type()) :
    mValue(pValue)
    {
        check_range(mValue, minimum, maximum);
    }

    const value_type& value(void) const
    {
        return mValue;
    }

    // arguably dangerous
    operator const value_type&(void) const
    {
        return mValue;
    }

private:
    value_type mValue;
};
Run Code Online (Sandbox Code Playgroud)

现在你有一个很好的实用程序,可以这样做:

typedef ranged_value<unsigned char, 0, 15> channel;

void foo(const channel& pChannel);
Run Code Online (Sandbox Code Playgroud)

它可以在其他场景中重复使用.只需将其全部保存在"checked_ranges.hpp"文件中,并在需要时使用它.制作抽象并不是坏事,并且使用实用程序并不是有害的.

此外,永远不要担心开销.创建一个类只需要运行你要做的相同代码.此外,干净的代码优先于其他任何东西; 表现是最后一个问题.完成后,您可以使用分析器来测量(而不是猜测)慢速部件的位置.

  • +1.考虑到这与我发布的内容有多么相似,如果我*没有*给它投票,那几乎是虚伪的!:-) (11认同)
  • 我喜欢这个主意! (2认同)

Jer*_*fin 27

是的,这个想法是值得的,但是(IMO)为每个整数范围编写一个完整的,单独的类是没有意义的.我遇到了足够的情况,需要调用有限范围的整数,我为此编写了一个模板:

template <class T, T lower, T upper>
class bounded { 
    T val;
    void assure_range(T v) {
        if ( v < lower || upper <= v)
            throw std::range_error("Value out of range");
    }
public:
    bounded &operator=(T v) { 
        assure_range(v);
        val = v;
        return *this;
    }

    bounded(T const &v=T()) {
        assure_range(v);
        val = v;
    }

    operator T() { return val; }
};
Run Code Online (Sandbox Code Playgroud)

使用它将是这样的:

bounded<unsigned, 0, 16> channel;
Run Code Online (Sandbox Code Playgroud)

当然,你可以比这更精细,但这个简单的仍然可以很好地处理大约90%的情况.

  • +1.考虑到这与我发布的内容有多么相似,如果我*没有*给它投票,那几乎是虚伪的!:-):P (10认同)

小智 14

不,这不是矫枉过正 - 你应该总是尝试将抽象表示为类.这样做有很多原因,而且开销很小.我会打电话给班级频道,而不是CChannel.


Sev*_*yev 11

不能相信到目前为止还没有人提到过枚举.不会给你一个防弹保护,但仍然比普通的整数数据类型更好.


Cos*_*ert 6

看起来有点矫枉过正,特别是operator unsigned char()配件.你没有封装数据,你使事情变得更加复杂,而且可能更容易出错.

像你这样的数据类型Channel通常是更抽象的东西的一部分.

所以,如果你在使用该类型ChannelSwitcher的类,你可以使用在评论的typedef正确ChannelSwitcher的身体(和可能,你的typedef将是public).

// Currently used channel type
typedef unsigned char Channel;
Run Code Online (Sandbox Code Playgroud)


Tom*_*ett 6

无论是在构造"CChannel"对象时抛出异常,还是在需要约束的方法入口处抛出异常都没什么区别.在任何一种情况下,你都在进行运行时断言,这意味着类型系统对你没有任何好处,是吗?

如果你想知道你可以用强类型语言走多远,答案是"很远,但不是用C++." 静态强制执行约束所需的权力,例如"此方法只能用0到15之间的数字调用"需要一些称为依赖类型的东西- 即依赖于值的类型.

要将概念放入伪C++语法(假装C++具有依赖类型),您可以这样写:

void Func(unsigned char channel, IsBetween<0, channel, 15> proof) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

请注意,它IsBetween是按而不是类型参数化的.为了在程序中调用此函数,必须向编译器提供第二个参数proof,该参数必须具有该类型IsBetween<0, channel, 15>.也就是说,你必须在0到15之间的编译时证明channel!这种代表命题的类型的概念,其值是这些命题的证明,被称为Curry-Howard函数.

当然,证明这些事情可能很困难.根据您的问题域,成本/收益比可以轻松地倾向于仅对您的代码进行运行时检查.