4

我经常发现自己使用整数来表示不同“空间”中的值。例如...

int arrayIndex;
int usersAge;
int daysToChristmas;

理想情况下,我想为这些类型“索引”、“年”和“天”中的每一个设置单独的类,这样可以防止我不小心把它们混在一起。从文档的角度来看,Typedef 是一种帮助,但不够类型安全。

我已经尝试过包装类,但最终我喜欢的样板太多。是否有一个简单的基于模板的解决方案,或者在 Boost 中是否有现成的解决方案?

编辑:有几个人在他们的答案中谈到了边界检查。这可能是一个方便的副作用,但不是关键要求。特别是,我不仅想防止超出范围的分配,还想防止“不适当”类型之间的分配。

4

10 回答 10

8

实际上,Boost 确实有一个专门用于此类事物的库!查看Boost.Units 库

于 2008-12-09T11:30:57.063 回答
7

您可以使用的一个时髦的“hack”是用于创建包装器类型的模板非类型参数。这不会添加任何界限,但它确实允许将它们视为仅具有一组样板模板代码的不同类型。IE

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;

根据需要使用下限和上限或其他验证扩展模板。不过从长远来看并不漂亮。

于 2008-12-09T10:44:40.073 回答
5

我记得用一个简单的模板解决了一个类似的问题,您可以在其中指定允许的范围,即

Int<0, 365> daysToChristmas;
Int<0, 150> usersAge;
Int<0, 6> dayOfWeek;

你明白了。现在您可以从这样的模板类型派生,例如

class DayOfYear: public Int<0, 365> {}

并且您不能再将用户年龄传递给期望 DayOfYear 的函数,并且您不必使用尖括号。

于 2008-12-09T10:46:21.817 回答
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.
于 2008-12-09T12:32:36.877 回答
4

这是一个通用的“StrongType”模板,我们用它来包装不同的类型和上下文。这个答案的唯一显着区别 是我们更喜欢使用一个标签类型,它为每个专门的包装器类型提供一个有意义的名称:

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;

还要注意,所有函数都是“内联”的,其目的是编译器可以尽最大努力生成与完全不使用模板时生成的代码完全相同的代码!

于 2008-12-09T11:13:07.020 回答
2

除了 Ryan Fox 提到的 Boost Units 库之外,还会有 Boost Constrained Value 库,目前正在审核中

谁知道它何时或是否会发布官方 Boost 版本,但无论如何你都可以尝试一下

于 2008-12-09T11:48:45.467 回答
1

添加运算符 int () 将允许您使用需要普通 int 的对象。您还可以添加运算符 = () 以将其设置在范围内。

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;

我希望这有帮助。

于 2008-12-09T10:54:41.613 回答
0
int arrayIndex;

这是std::size_t为了什么。

int usersAge;

人们不能有负年龄,并且设置年龄的固定上限是没有用/容易的。所以在这里你应该只使用unsigned int.

int daysToChristmas;

圣诞节前几天需要特别注意。圣诞节前的天数范围为 0-366。简单的解决方案是在需要的地方编写以下内容:

assert( 0 < daysToChristmas && daysToChristmas < 366 )

如果你觉得你要assert在太多地方重复它,那么 David Allan Finch 为这个案例提出了一个巧妙的解决方案。虽然我偏爱使用断言。

于 2008-12-09T11:12:03.200 回答
0

对于数组索引,如果我不需要负值,我会使用 size_t,因为这就是它的用途。当然,这通常是无符号整数,所以根本不会给你任何类型安全。但是,任何确实为您提供类型安全的东西(即阻止您将 unsigned int 分配给数组索引)也会阻止您将 size_t 值返回到您的类型中。无论如何,这可能是过多的类型安全。

您可能可以将枚举用于有界范围:

enum YearDay {
    FirstJan = 0,
    LastDecInLeapYear = 365
};

您可以将 YearDay 分配给 int,但如果没有显式强制转换,则不能将 int(或其他枚举类型)分配给 YearDay。枚举中最小和最大命名值之间的任何值都是该枚举的有效值。分配超出范围 [0,365] 的值会导致未定义的行为。或者可能是未指定或实现定义的结果,我不记得了。

年龄很棘手,因为它几乎是有界的,但并不完全。您可以在枚举中使用 969(玛士撒拉时代),或者使用其他人描述的使用显式转换包装 int 的类。

于 2008-12-09T11:38:27.157 回答
0

查看有关此主题的旧 CUJ 文章。IIRC 该技术描述了如何使其与所有基本运算符一起使用

于 2009-03-17T05:42:47.277 回答