11

我正在尝试编写一个 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 的任何东西都是正确的。

4

10 回答 10

12

std::numeric_limits您可以使用模板以更优雅的方式获取任何基本类型的最小和最大安全值(以及大量其他信息) ,例如std::numeric_limits<T>::max(). 您需要包括<limits>.

参考:http ://www.cplusplus.com/reference/std/limits/numeric_limits/

于 2009-06-15T21:51:44.037 回答
11

提升是一种选择吗?如果是这样,请尝试boost::numeric_cast<>。它似乎提供了您正在寻找的特征。

于 2009-06-15T22:10:05.270 回答
8

我认为这些现在都有效,无论您是否使用二进制补码。请在使用前进行广泛的测试。他们给出以下结果。每一行给出一个断言失败(只需将它们更改为您喜欢的异常)

/* 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); }
于 2009-06-15T23:39:01.983 回答
5

你试过SafeInt吗?它是一个跨平台模板,将对各种整数类型进行整数溢出检查。可以在 github 上找到

于 2009-06-15T22:12:41.833 回答
3

怎么样:

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 的转换会引发异常(应该如此)。我认为这里没有问题。

于 2009-06-15T23:51:17.960 回答
2

在https://www.boost.org/doc/libs/1_71_0/libs/safe_numerics/doc/html/index.html http://rrsd.com/blincubator.com/bi_library/safe-numerics考虑安全数值

该库为所有 C 原始整数类型提供了直接替换。导致错误结果的 C 操作(包括强制转换)在检测到时会被捕获。

于 2014-11-06T21:26:11.673 回答
2

自发布此问题以来已有十多年了,我想要一个自包含并使用现代 C++ ( std::optional, constexpr, type_traits) 的解决方案。这是我写的:

/// Cast integer of type "From" to integer of type "To", as long as it fits. If it doesn't
/// fit, return std::nullopt.
template<typename To, typename From>
constexpr std::optional<To> IntegerCast(From from) {
    static_assert(std::is_integral_v<From>, "IntegerCast only supports integers");
    static_assert(std::is_integral_v<To>, "IntegerCast only supports integers");
    static_assert(!std::is_same_v<To, bool>, "IntegerCast only supports integers");
    static_assert(!std::is_same_v<From, bool>, "IntegerCast only supports integers");

    constexpr bool fromSigned = std::is_signed_v<From>;
    constexpr bool toSigned = std::is_signed_v<To>;
    constexpr bool bothSigned = fromSigned && toSigned;
    constexpr bool bothUnsigned = !fromSigned && !toSigned;

    constexpr From fromMax = std::numeric_limits<From>::max();
    constexpr From fromMin = std::numeric_limits<From>::min();
    constexpr To toMax = std::numeric_limits<To>::max();
    constexpr To toMin = std::numeric_limits<To>::min();

    if constexpr (bothUnsigned) {
        using Widen = std::conditional_t<(sizeof(From) > sizeof(To)), From, To>;
        if (from > Widen(toMax)) {
            return std::nullopt;
        } else {
            return To(from);
        }
    } else if constexpr (bothSigned) {
        using Widen = std::conditional_t<(sizeof(From) > sizeof(To)), From, To>;
        if (from > Widen(toMax)) {
            return std::nullopt;
        } else if (from < Widen(toMin)) {
            return std::nullopt;
        } else {
            return To(from);
        }
    } else if constexpr (fromSigned && !toSigned) {
        using Widen =
                std::make_unsigned_t<std::conditional_t<(sizeof(From) > sizeof(To)), From, To>>;
        if (from < 0) {
            return std::nullopt;
        } else if (from > Widen(toMax)) {
            return std::nullopt;
        } else {
            return To(from);
        }
    } else if constexpr (!fromSigned && toSigned) {
        using Widen =
                std::make_unsigned_t<std::conditional_t<(sizeof(From) > sizeof(To)), From, To>>;
        if (from > Widen(toMax)) {
            return std::nullopt;
        } else {
            return To(from);
        }
    }
}

它带有 GoogleTest 中的测试套件

TEST(IntegerCast, Basics) {
    constexpr uint64_t large64 = 10000000000000000000ull;
    static_assert(IntegerCast<uint8_t>(large64) == std::nullopt);
    static_assert(IntegerCast<uint16_t>(large64) == std::nullopt);
    static_assert(IntegerCast<uint32_t>(large64) == std::nullopt);
    static_assert(IntegerCast<uint64_t>(large64) == 10000000000000000000ull);
    static_assert(IntegerCast<int8_t>(large64) == std::nullopt);
    static_assert(IntegerCast<int16_t>(large64) == std::nullopt);
    static_assert(IntegerCast<int32_t>(large64) == std::nullopt);
    static_assert(IntegerCast<int64_t>(large64) == std::nullopt);

    constexpr int64_t largeNegative64 = -5000000000000000000;
    static_assert(IntegerCast<uint8_t>(largeNegative64) == std::nullopt);
    static_assert(IntegerCast<uint16_t>(largeNegative64) == std::nullopt);
    static_assert(IntegerCast<uint32_t>(largeNegative64) == std::nullopt);
    static_assert(IntegerCast<uint64_t>(largeNegative64) == std::nullopt);
    static_assert(IntegerCast<int8_t>(largeNegative64) == std::nullopt);
    static_assert(IntegerCast<int16_t>(largeNegative64) == std::nullopt);
    static_assert(IntegerCast<int32_t>(largeNegative64) == std::nullopt);
    static_assert(IntegerCast<int64_t>(largeNegative64) == -5000000000000000000);

    constexpr uint64_t small64 = 1;
    static_assert(IntegerCast<uint8_t>(small64) == 1);
    static_assert(IntegerCast<uint16_t>(small64) == 1);
    static_assert(IntegerCast<uint32_t>(small64) == 1);
    static_assert(IntegerCast<uint64_t>(small64) == 1);
    static_assert(IntegerCast<int8_t>(small64) == 1);
    static_assert(IntegerCast<int16_t>(small64) == 1);
    static_assert(IntegerCast<int32_t>(small64) == 1);
    static_assert(IntegerCast<int64_t>(small64) == 1);

    constexpr int64_t smallNegative64 = -1;
    static_assert(IntegerCast<uint8_t>(smallNegative64) == std::nullopt);
    static_assert(IntegerCast<uint16_t>(smallNegative64) == std::nullopt);
    static_assert(IntegerCast<uint32_t>(smallNegative64) == std::nullopt);
    static_assert(IntegerCast<uint64_t>(smallNegative64) == std::nullopt);
    static_assert(IntegerCast<int8_t>(smallNegative64) == -1);
    static_assert(IntegerCast<int16_t>(smallNegative64) == -1);
    static_assert(IntegerCast<int32_t>(smallNegative64) == -1);
    static_assert(IntegerCast<int64_t>(smallNegative64) == -1);
}

TEST(IntegerCast, Boundaries) {
    constexpr uint8_t maxUnsigned8 = 255;
    static_assert(IntegerCast<uint8_t>(maxUnsigned8) == 255);
    static_assert(IntegerCast<uint16_t>(maxUnsigned8) == 255);
    static_assert(IntegerCast<uint32_t>(maxUnsigned8) == 255);
    static_assert(IntegerCast<uint64_t>(maxUnsigned8) == 255);
    static_assert(IntegerCast<int8_t>(maxUnsigned8) == std::nullopt);
    static_assert(IntegerCast<int16_t>(maxUnsigned8) == 255);
    static_assert(IntegerCast<int32_t>(maxUnsigned8) == 255);
    static_assert(IntegerCast<int64_t>(maxUnsigned8) == 255);

    constexpr uint8_t minUnisigned8 = 0;
    static_assert(IntegerCast<uint8_t>(minUnisigned8) == 0);
    static_assert(IntegerCast<uint16_t>(minUnisigned8) == 0);
    static_assert(IntegerCast<uint32_t>(minUnisigned8) == 0);
    static_assert(IntegerCast<uint64_t>(minUnisigned8) == 0);
    static_assert(IntegerCast<int8_t>(minUnisigned8) == 0);
    static_assert(IntegerCast<int16_t>(minUnisigned8) == 0);
    static_assert(IntegerCast<int32_t>(minUnisigned8) == 0);
    static_assert(IntegerCast<int64_t>(minUnisigned8) == 0);

    constexpr int8_t maxSigned8 = 127;
    static_assert(IntegerCast<uint8_t>(maxSigned8) == 127);
    static_assert(IntegerCast<uint16_t>(maxSigned8) == 127);
    static_assert(IntegerCast<uint32_t>(maxSigned8) == 127);
    static_assert(IntegerCast<uint64_t>(maxSigned8) == 127);
    static_assert(IntegerCast<int8_t>(maxSigned8) == 127);
    static_assert(IntegerCast<int16_t>(maxSigned8) == 127);
    static_assert(IntegerCast<int32_t>(maxSigned8) == 127);
    static_assert(IntegerCast<int64_t>(maxSigned8) == 127);

    constexpr int8_t minSigned8 = -128;
    static_assert(IntegerCast<uint8_t>(minSigned8) == std::nullopt);
    static_assert(IntegerCast<uint16_t>(minSigned8) == std::nullopt);
    static_assert(IntegerCast<uint32_t>(minSigned8) == std::nullopt);
    static_assert(IntegerCast<uint64_t>(minSigned8) == std::nullopt);
    static_assert(IntegerCast<int8_t>(minSigned8) == -128);
    static_assert(IntegerCast<int16_t>(minSigned8) == -128);
    static_assert(IntegerCast<int32_t>(minSigned8) == -128);
    static_assert(IntegerCast<int64_t>(minSigned8) == -128);
}

TEST(IntegerCast, SameSizeDifferentSign) {
    constexpr uint8_t above = 200;
    static_assert(IntegerCast<int8_t>(above) == std::nullopt);

    constexpr uint8_t withinUnsigned = 100;
    static_assert(IntegerCast<int8_t>(withinUnsigned) == 100);

    constexpr int8_t withinSigned = 100;
    static_assert(IntegerCast<uint8_t>(withinSigned) == 100);

    constexpr int8_t below = -100;
    static_assert(IntegerCast<uint8_t>(below) == std::nullopt);
}
于 2020-05-02T00:36:30.240 回答
1

我是否正确假设在 R 已签名的情况下,您试图用除最后一位之外的所有 1 填充 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 );
}

编辑:修复错误。

于 2009-06-15T22:15:26.910 回答
0

我在sweet.hpp有一个名为conv.hpp的标题。它将测试所有整数类型的界限,并且还允许对整数进行字符串转换。

short a = to<short>(1337);
std::string b = to<std::string>(a);
long c = to<long>(b);
于 2014-02-18T10:06:15.673 回答
-2

我一定错过了什么,但这不是你想要的吗?:

// 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;
}
于 2009-06-16T04:58:33.913 回答