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"文件中,并在需要时使用它.制作抽象并不是坏事,并且使用实用程序并不是有害的.
此外,永远不要担心开销.创建一个类只需要运行你要做的相同代码.此外,干净的代码优先于其他任何东西; 表现是最后一个问题.完成后,您可以使用分析器来测量(而不是猜测)慢速部件的位置.
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%的情况.
看起来有点矫枉过正,特别是operator unsigned char()配件.你没有封装数据,你使事情变得更加复杂,而且可能更容易出错.
像你这样的数据类型Channel通常是更抽象的东西的一部分.
所以,如果你在使用该类型ChannelSwitcher的类,你可以使用在评论的typedef正确ChannelSwitcher的身体(和可能,你的typedef将是public).
// Currently used channel type
typedef unsigned char Channel;
Run Code Online (Sandbox Code Playgroud)
无论是在构造"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函数.
当然,证明这些事情可能很困难.根据您的问题域,成本/收益比可以轻松地倾向于仅对您的代码进行运行时检查.