Шаблон C ++ для безопасных целочисленных приведений - PullRequest
10 голосов
/ 16 июня 2009

Я пытаюсь написать шаблонную функцию C ++, которая будет генерировать исключение времени выполнения при целочисленном переполнении при приведении между различными целочисленными типами, с различной шириной и возможным несоответствием со знаком или без знака. Для этих целей меня не интересует приведение типов с плавающей точкой к целочисленным типам или другие преобразования объектов в объекты. Я хотел бы сделать это без необходимости писать много кода для особых случаев. Вот что у меня сейчас есть:

template< typename T, typename R > void safe_cast( const T& source, R& result )
{
    // get the maximum safe value of type R
    R rMax = (R) ~0;
    if ( rMax < 0 ) // R is a signed type
    {
        // assume that we're on an 8-bit twos-compliment machine
        rMax = ~( 0x80 << ( ( sizeof( R ) - 1 ) * 8 ) );
    }

    if ( ( source & rMax  ) != source )
    {
        throw new IntegerOverflowException( source );
    }

    result = static_cast<R>( source );
}

Это правильно и эффективно?

РЕДАКТИРОВАТЬ: по разным причинам stl не доступен, поэтому я не могу использовать std :: numeric_limits, и что-то из Boost прямо.

Ответы [ 9 ]

12 голосов
/ 16 июня 2009

Вы можете получить минимальное и максимальное безопасные значения (и целый ряд другой информации) для любого базового типа гораздо более элегантным способом, используя шаблон std::numeric_limits, например std::numeric_limits<T>::max(). Вам нужно будет включить <limits>.

Ссылка: http://www.cplusplus.com/reference/std/limits/numeric_limits/

11 голосов
/ 16 июня 2009

Опция повышения - вариант? Если это так, попробуйте boost :: numeric_cast <> . Похоже, чтобы обеспечить характеристики, которые вы ищете.

7 голосов
/ 16 июня 2009

Я думаю, что они работают сейчас, независимо от того, используете ли вы два дополнения или нет. Пожалуйста, тщательно протестируйте, прежде чем использовать его. Они дают следующие результаты. Каждая строка дает один сбой утверждения (просто измените их как исключения)

/* unsigned -> signed, overflow */
safe_cast<short>(UINT_MAX);

/* unsigned -> unsigned, overflow */
safe_cast<unsigned char>(ULONG_MAX);

/* signed -> unsigned, overflow */
safe_cast<unsigned long>(-1);

/* signed -> signed, overflow */
safe_cast<signed char>(INT_MAX);

/* always works (no check done) */
safe_cast<long>(INT_MAX);

// giving these assertion failures results
(type)f <= (type)is_signed<To>::v_max
f <= (To)-1
f >= 0
f >= is_signed<To>::v_min && f <= is_signed<To>::v_max

Осуществление. Сначала некоторые утилиты проверяют целочисленные ранги (типы с более высокими рангами смогут содержать значения типов с более низким рангом при одинаковом знаке. И некоторые инструменты продвижения, чтобы иметь возможность определить общий безопасный тип (это никогда выдает тип со знаком, если задействован тип без знака, если тип со знаком не сможет хранить все значения без знака).

/* ranks */
template<typename> struct int_rank;
#define RANK(T, I) template<> struct int_rank<T> \
    { static int const value = I; }

RANK(char, 1); RANK(unsigned char, 1); RANK(signed char, 1); 
RANK(short, 2); RANK(unsigned short, 2);
RANK(int, 3); RANK(unsigned int, 3);
RANK(long, 4); RANK(unsigned long, 4);
#undef RANK

/* usual arith. conversions for ints (pre-condition: A, B differ) */
template<int> struct uac_at;
template<> struct uac_at<1> { typedef int type; };
template<> struct uac_at<2> { typedef unsigned int type; };
template<> struct uac_at<3> { typedef long type; };
template<> struct uac_at<4> { typedef unsigned long type; };

template<typename A, typename B>
struct uac_type { 
    static char (&f(int))[1];
    static char (&f(unsigned int))[2];
    static char (&f(long))[3];
    static char (&f(unsigned long))[4];
    typedef typename uac_at<sizeof f(0 ? A() : B())>::type type; 
};

/* signed games */
template<typename> struct is_signed { static bool const value = false; };
#define SG(X, TT) template<> struct is_signed<X> { \
    static bool const value = true;                \
    static X const v_min = TT##_MIN;               \
    static X const v_max = TT##_MAX;               \
}

SG(signed char, SCHAR); 
SG(short, SHRT); 
SG(int, INT); 
SG(long, LONG); 
#undef SG

template<> struct is_signed<char> { 
    static bool const value = (CHAR_MIN < 0); 
    static char const v_min = CHAR_MIN; // just in case it's signed...
    static char const v_max = CHAR_MAX;
};

Шаблоны преобразования используют их, чтобы выяснить для каждого случая, когда нужно что-то делать или не делать.

template<typename To, typename From, 
         bool to_signed = is_signed<To>::value, 
         bool from_signed = is_signed<From>::value,
         bool rank_fine = (int_rank<To>::value >= int_rank<From>::value)>
struct do_conv;

/* these conversions never overflow, like int -> int, 
 * or  int -> long. */
template<typename To, typename From, bool Sign>
struct do_conv<To, From, Sign, Sign, true> {
    static To call(From f) {
        return (To)f; 
    }
};

template<typename To, typename From>
struct do_conv<To, From, false, false, false> {
    static To call(From f) {
        assert(f <= (To)-1);
        return (To)f;
    }
};

template<typename To, typename From>
struct do_conv<To, From, false, true, true> {
    typedef typename uac_type<To, From>::type type;
    static To call(From f) {
        /* no need to check whether To's positive range will
         * store From's positive range: Because the rank is
         * fine, and To is unsigned. 
         * Fixes GCC warning "comparison is always true" */
        assert(f >= 0);
        return (To)f;
    }
};

template<typename To, typename From>
struct do_conv<To, From, false, true, false> {
    typedef typename uac_type<To, From>::type type;
    static To call(From f) {
        assert(f >= 0 && (type)f <= (type)(To)-1);
        return (To)f;
    }
};

template<typename To, typename From, bool Rank>
struct do_conv<To, From, true, false, Rank> {
    typedef typename uac_type<To, From>::type type;
    static To call(From f) {
        assert((type)f <= (type)is_signed<To>::v_max);
        return (To)f;
    }
};

template<typename To, typename From>
struct do_conv<To, From, true, true, false> {
    static To call(From f) {
        assert(f >= is_signed<To>::v_min && f <= is_signed<To>::v_max);
        return (To)f;
    }
};

template<typename To, typename From>
To safe_cast(From f) { return do_conv<To, From>::call(f); }
5 голосов
/ 16 июня 2009

Вы пробовали SafeInt? Это кроссплатформенный шаблон, который будет выполнять проверку целочисленного переполнения для целого ряда типов. Это доступно на github

3 голосов
/ 16 июня 2009

Как насчет:

template< typename T, typename R > void safe_cast( const T& source, R& result )
{
    R temp = static_cast<R>( source );
    if (static_cast<T> (temp) != source
        || ( temp < 0 && source > 0)
        || ( temp > 0 && source < 0))
    {
        throw IntegerOverflowException( source );
    }
    result = temp;
}

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

EDIT: Так как комментарий ниже запутался, вот он, отформатированный:

int myint (-1);
safe_cast( myint, mychar );
safe_cast( mychar, myuchar ); // Exception is thrown here
safe_cast( myuchar, myint );

Приведение от int к char работает нормально. Преобразование из char в unsigned char вызывает исключение (как и должно быть). Я не вижу здесь проблемы.

1 голос
/ 07 ноября 2014

рассмотрите Безопасные Числа в http://rrsd.com/blincubator.com/bi_library/safe-numerics

Эта библиотека предоставляет вставные замены для всех целочисленных типов примитивов Си. Операции C, которые приводят к ошибочным результатам, в том числе приведение в ловушку при обнаружении.

1 голос
/ 16 июня 2009

Правильно ли я предполагаю, что в случае, если R подписан, вы пытаетесь заполнить rMax всеми 1, кроме последнего бита? Если это так, то у вас должно быть 0x80 (1000 0000) вместо 0x10 (0001 0000).

Также не похоже, что ваша функция поддерживает отрицательные числа для источника.

Edit:

Вот слегка отредактированная версия, которую я протестировал для преобразования целых в символы:

template< typename T, typename R >
void safe_cast( const T& source, R& result )
{
    // get the maximum safe value of type R
    R rMax = (R) ~0;
    if ( rMax < 0 ) // R is a signed type
    {
        // assume that we're on an 8-bit twos-compliment machine
    rMax = ( 0x80 << ( ( sizeof( R ) - 1 ) * 8 ) );
    if(source >= 0)
        rMax = ~rMax;
    }

    if ( (source >= 0 && ( source & rMax  ) != source) || (source < 0 && (source & rMax) != rMax) )
    {
        throw new IntegerOverflowException( source );
    }

    result = static_cast<R>( source );
}

Редактировать: исправлена ​​ошибка.

0 голосов
/ 18 февраля 2014

У меня есть один заголовок на sweet.hpp , который называется conv.hpp . Он будет проверять границы для всех целочисленных типов, а также позволяет выводить строки из целых чисел и из них.

short a = to<short>(1337);
std::string b = to<std::string>(a);
long c = to<long>(b);
0 голосов
/ 16 июня 2009

Я, должно быть, что-то упустил, но разве это не то, что вы хотите?:

// using a more cast-like prototype, if I may:
template<class to, class from> inline
to safe_cast(from f)
{
   to t = static_cast<to>(f);
   if ( t != f ) throw whatever; // no new!
   return t;
}
...