Несколько классов целочисленного типа в C ++ - PullRequest
4 голосов
/ 09 декабря 2008

Я часто использую целые числа для представления значений в разных «пробелах». Например ...

int arrayIndex;
int usersAge;
int daysToChristmas;

В идеале я хотел бы иметь отдельные классы для каждого из этих типов "Индекс", "Годы" и "Дни", что должно предотвратить случайное их перепутывание. Typedefs - это помощь с точки зрения документирования, но они недостаточно безопасны для типов.

Я пробовал классы-обертки, но в итоге получилось слишком много шаблонов для моего вкуса Есть ли простое решение на основе шаблонов или, может быть, что-то готовое в Boost?

РЕДАКТИРОВАТЬ: Несколько человек говорили о проверке границ в своих ответах. Это может быть удобным побочным эффектом, но НЕ является ключевым требованием. В частности, я хочу не просто предотвращать недопустимые присваивания, но и присваивать «неуместные» типы.

Ответы [ 10 ]

8 голосов
/ 09 декабря 2008

У Boost действительно есть библиотека специально для этого типа вещей! Проверьте библиотеку Boost.Units .

7 голосов
/ 09 декабря 2008

Один из возможных «хаков», который вы можете использовать, - это шаблонный тип для создания типов-оболочек. Это не добавляет никаких границ, но действительно позволяет рассматривать их как различные типы только с одним набором шаблонного кода шаблона. * Т.е. 1003 *

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;

Расширьте шаблон с нижними и верхними границами или другими проверками, как вам нравится. Хотя и не очень долго.

5 голосов
/ 09 декабря 2008

Вы можете попробовать 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.
5 голосов
/ 09 декабря 2008

Я помню, как решал похожую проблему с помощью простого шаблона, в котором вы бы указали допустимый диапазон, т.е.

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

Ты понял. Теперь вы можете просто извлечь из такого типа шаблона, как

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

и вы больше не можете передавать возраст пользователя функции, ожидающей DayOfYear, и вам не придется использовать угловые скобки.

4 голосов
/ 09 декабря 2008

Это универсальный шаблон "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;

Также обратите внимание, что все функции являются «встроенными», при этом предполагается, что компилятор может сделать все возможное, чтобы сгенерировать точно такой же код, который он сгенерировал бы, если бы шаблон вообще не использовался!

2 голосов
/ 09 декабря 2008

В дополнение к библиотеке Boost Units, упомянутой Райаном Фоксом, будет также библиотека Boost Constrained Value, которая в настоящее время находится на рассмотрении .

Кто знает, когда или если он выйдет в официальном выпуске Boost, но вы, вероятно, можете попробовать его в любом случае .

1 голос
/ 09 декабря 2008

Добавление в оператор 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;

Надеюсь, это поможет.

0 голосов
/ 17 марта 2009

Проверьте эту старую статью CUJ на эту тему. IIRC метод описывает, как заставить его работать со всеми основными операторами

0 голосов
/ 09 декабря 2008

Для индекса массива я бы использовал size_t при условии, что мне не нужны отрицательные значения, потому что для этого он и нужен. Конечно, это часто - unsigned int, поэтому вообще не даст вам никакой безопасности типов. Однако все, что обеспечило вам безопасность типов (т. Е. Остановило назначение целого числа без знака для индекса массива), также остановит возврат значения size_t в ваш тип. В любом случае, это может быть слишком много безопасности типов.

Возможно, вы можете использовать enum для ограниченных диапазонов:

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

Вы можете назначить YearDay для int, но вы не можете назначить int (или другой тип перечисления) для YearDay без явного приведения. Любое значение между наименьшим и наибольшим именованным значением в перечислении является допустимым значением для перечисления. Присвоение значения за пределы диапазона [0,365] приводит к неопределенному поведению. Или, возможно, неуказанный или определенный реализацией результат, я не могу вспомнить.

Возраст сложен, потому что он почти ограничен, но не совсем. Вы можете использовать 969 (возраст Метузела) в перечислении или класс, заключающий int в явные преобразования, как описано другими.

0 голосов
/ 09 декабря 2008
int arrayIndex;

Для этого std::size_t.

int usersAge;

Люди не могут иметь отрицательный возраст, и нецелесообразно / легко устанавливать фиксированную верхнюю границу для возрастов. Так что здесь вы должны просто использовать unsigned int.

int daysToChristmas;

Дней до Рождества требует особого внимания. Количество дней до Рождества может варьироваться от 0 до 366. Простое решение - написать где угодно:

assert( 0 < daysToChristmas && daysToChristmas < 366 )

Если вы чувствуете, что собираетесь скопировать этот assert в слишком многих местах, то Дэвид Аллан Финч предлагает подходящее решение для этого случая. Хотя я неравнодушен к использованию assert.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...