我经常发现自己使用Integers来表示不同“空格”中的值。例如......
int arrayIndex;
int usersAge;
int daysToChristmas;
理想情况下,我希望为“Index”,“Years”和“Days”这些类型中的每一种设置单独的类,这样可以防止我意外混淆它们。从documnentation的角度来看,Typedef是一种帮助,但不够类型安全。
我已经尝试过包装类,但最终却有太多的样板供我喜欢。是否有一个简单的基于模板的解决方案,或者可能是Boost中准备好的东西?
编辑:有几个人在他们的答案中谈到边界检查。这可能是一个方便的副作用,但不是一个关键要求。特别是,我不仅想要防止越界分配,而且要防止“不适当”类型之间的分配。答案 0 :(得分:8)
答案 1 :(得分:7)
您可以使用的一个时髦的“黑客”是用于创建包装类型的模板非类型参数。这不会添加任何边界,但 允许将它们视为只有一组样板模板代码的不同类型。即。
template<unsigned i>
class t_integer_wrapper
{
private:
int m_value;
public:
// Constructors, accessors, operators, etc.
};
typedef t_integer_wrapper<1> ArrayIndex;
typedef t_integer_wrapper<2> UsersAge;
根据需要使用下限和上限或其他验证扩展模板。虽然远远不够漂亮。
答案 2 :(得分:5)
我记得用简单的模板解决了类似的问题,你可以在其中指定允许的范围,即
Int<0, 365> daysToChristmas;
Int<0, 150> usersAge;
Int<0, 6> dayOfWeek;
你明白了。现在你可以从这样的模板类型派生出来,比如
class DayOfYear: public Int<0, 365> {}
并且您无法再将用户年龄传递给期望DayOfYear的函数,并且您不必使用带角度的括号。
答案 3 :(得分:5)
您可以尝试BOOST_STRONG_TYPEDEF。来自boost/strong_typedef.hpp
:
// macro used to implement a strong typedef. strong typedef
// guarentees that two types are distinguised even though the
// share the same underlying implementation. typedef does not create
// a new type. BOOST_STRONG_TYPEDEF(T, D) creates a new type named D
// that operates as a type T.
答案 4 :(得分:4)
这是一个通用的“StrongType”模板,我们用它来包装不同的类型和上下文。这个answer的唯一显着区别是我们更喜欢使用标记类型,它为每个专门的包装类型提供了一个有意义的名称:
template <typename ValueType, class Tag> class StrongType {
public:
inline StrongType() : m_value(){}
inline explicit StrongType(ValueType const &val) : m_value(val) {}
inline operator ValueType () const {return m_value; }
inline StrongType & operator=(StrongType const &newVal) {
m_value = newVal.m_value;
return *this;
}
private:
//
// data
ValueType m_value;
};
使用模板如下:
class ArrayIndexTag;
typedef StringType<int, ArrayIndexTag> StrongArrayIndex;
StringArrayIndex arrayIndex;
另外请注意,所有函数都是'inline',意图是编译器可以尽力生成完全相同的代码,如果模板根本没有使用的话!
答案 5 :(得分:2)
除了Ryan Fox提到的Boost Units库之外,还有一个Boost Constrained Value库,currently under review。
谁知道何时或是否会发布官方Boost版本,但您可能try it out anyway。
答案 6 :(得分:1)
添加运算符int()将允许您使用需要普通int的对象。您还可以添加operator =()以将其设置在范围内。
class DayType
{
public:
static int const low = 1;
static int const high = 365;
};
template<class TYPE>
class Int
{
private:
int m_value;
public:
operator int () { return m_value; }
operator = ( int i ) { /* check and set*/ }
};
Int<DayType> day;
int d = day;
day = 23;
我希望这会有所帮助。
答案 7 :(得分:0)
int arrayIndex;
这是std::size_t
的用途。
int usersAge;
人们不能有负面年龄,并且为年龄设定固定的上限是没有用的/容易的。所以在这里你应该使用unsigned int
。
int daysToChristmas;
圣诞节需要特别注意。 Christamas的天数范围为0-366。简单的解决方案是在需要的地方写下以下内容:
assert( 0 < daysToChristmas && daysToChristmas < 366 )
如果您觉得要在太多地方复制assert
,那么David Allan Finch就此案例提出了一个简洁的解决方案。虽然我偏爱使用断言。
答案 8 :(得分:0)
对于数组索引我会使用size_t,前提是我不需要负值,因为这就是它的用途。当然,这通常是unsigned int,因此根本不会给你任何类型安全。但是,任何确实为您提供类型安全性的东西(即停止将无符号整数分配给数组索引)也会阻止您将size_t值返回到您的类型中。无论如何,这可能是太多的类型安全。
你可以使用enum作为有界范围:
enum YearDay {
FirstJan = 0,
LastDecInLeapYear = 365
};
您可以将YearDay指定给int,但是如果没有显式强制转换,则无法为YearDay指定int(或其他枚举类型)。枚举中最小和最大命名值之间的任何值都是枚举的有效值。分配超出范围[0,365]的值会导致未定义的行为。或者可能是未指定或实现定义的结果,我不记得了。
年龄很棘手,因为几乎有界,但并不完全。您可以在枚举中使用969(Methuselah的年龄),或者使用其他人描述的显式转换来包装int的类。
答案 9 :(得分:0)
在此主题上查看此old CUJ article。 IIRC该技术旨在使其与所有基本运营商合作