Вопрос

Я пытаюсь написать функцию шаблона 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 подходит.

Это было полезно?

Решение

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

Другие советы

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

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

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

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

/* 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); }

Как насчет:

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;
}

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

РЕДАКТИРОВАТЬ:Поскольку комментарий ниже запутался, вот он в формате:

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

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

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

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

Редактировать:

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

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 );
}

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

рассмотрите безопасные цифры на http://rrsd.com/blincubator.com/bi_library/safe-numerics

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

Должно быть, я что-то упускаю, но разве это не то, что вам нужно?:

// 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;
}

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

short a = to<short>(1337);
std::string b = to<std::string>(a);
long c = to<long>(b);
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top