2009-06-15 18 views
8

Estoy tratando de escribir una función de plantilla C++ que arrojará una excepción de tiempo de ejecución en el desbordamiento de enteros en moldes entre diferentes tipos integrales, con diferentes anchos, y posible falta de coincidencia firmado/no firmado. A estos efectos, no me preocupo por la conversión desde tipos de coma flotante a tipos integrales ni otras conversiones de objeto a objeto. Me gustaría hacer esto sin tener que escribir muchos códigos de casos especiales. Esto es lo que tengo actualmente:C++ Plantilla para entero seguro moldes

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

¿Es esto correcto y eficiente?

EDITAR: Por varias razones, stl no está disponible, por lo que no puedo usar std :: numeric_limits, y cualquier cosa de Boost es correcta.

+0

Pero puede copiar el código que necesita de numeric_limits en su asistente de plantilla. Asigne todo a uint64 (o cualquiera que sea el tamaño máximo permitido) y haga comparaciones dentro de ese tipo. –

+1

Eso podría funcionar, pero uno realmente necesita conocer los términos de la licencia al copiar código como este. Además de violar potencialmente los términos, uno podría "infectar" inadvertidamente su código, como es el caso con la GPL. Asegúrese de que ambas licencias sean compatibles antes de hacer este tipo de cosas. Se aplica el descargo de responsabilidad habitual de "No soy abogado". – Void

+0

¿Cuáles son las diversas razones por las que no puede usar el STL? – GManNickG

Respuesta

5

¿Has probado SafeInt? Es una plantilla multiplataforma que realizará comprobaciones de desbordamiento de enteros para una variedad de tipos enteros. Está disponible en CodePlex

12

Puede obtener los valores mínimos y máximos seguros (y una gran cantidad de otra información) para cualquier tipo fundamental de una manera mucho más elegante con la plantilla std::numeric_limits, p. std::numeric_limits<T>::max(). Tendrá que incluir <limits>.

Referencia: http://www.cplusplus.com/reference/std/limits/numeric_limits/

+0

esta edición 7 años después me hizo cambiar mi voto popular a un voto a favor. Porque el ejemplo presentado por @ jpo38 no funciona. Par de ejemplo: From = int, To = unsigned. fuente == - 1. –

+0

En realidad, lo vi y lo arreglé en mi código desde que edité esta publicación. Pero olvidé actualizar la edición ... lo siento. Ahora arrojo si 'static_cast (static_cast (fuente))! = Source' (básicamente, si se pierde algo de información por el elenco ... esto funciona muy bien. De alguna manera similar a lo que Tim propone a continuación. Sin referencia a max/min el deseo falla al pasar de firmado a no firmado y el camino correcto. – jpo38

11

es impulsar una opción? Si es así, intente boost::numeric_cast<>. Parece proporcionar las características que estás buscando.

1

Estoy en lo cierto al suponer que en el caso de que R se firmó usted está tratando de llenar Rmax con todos 1 a excepción de la última parte? Si ese es el caso, entonces debe tener 0x80 (1000 0000) en lugar de 0x10 (0001 0000).

Además, no parece que su función admita números negativos para la fuente.

Editar:

Aquí es una versión ligeramente editado que he probado para la conversión de enteros de caracteres:

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

Editar: error fijo.

7

Creo que estos funcionan ahora, independientemente de si usa el complemento de dos o no. Por favor, prueba ampliamente antes de usarlo. Dan los siguientes resultados. Cada línea da una falla de afirmación (simplemente cámbielas a excepciones como quiera)

/* 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 

Implementación. Primero, algunas utilidades para verificar los rangos enteros (los tipos con rangos más altos podrán contener valores de tipos con menor rango, con el mismo signo. Y algunas herramientas de promoción, para poder descifrar un tipo común y seguro (esto nunca producir un tipo con signo si se trata de un tipo sin signo, si el tipo firmado no será capaz de almacenar todos los valores del uno sin firmar).

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

las plantillas de conversión hacer uso de ellos, de averiguar para cada caso cuando lo que debe hacerse o no.

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

¿Qué tal:

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

Entonces usted está comprobando si el casting funcionó. Asegúrate de recuperar lo que comenzaste y que el letrero no se volteó.

EDIT: Desde el comentario anterior quedó en mal estado, aquí está, el formato:

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

El reparto de int a carbón funciona bien. El reparto de char a char sin signo arroja una excepción (como debería). No veo un problema aquí.

+0

o, el compilador escupirá advertencias cuando uno de los dos tipos no está firmado y el otro no (la comparación es siempre falsa debido al rango limitado del tipo de datos en g ++) cuando comparando con 0, pero no se lanzará y los datos se perderán: Si usa su conversión de -1 (int) a -1 (char) a 0xFF (char sin signo) volver a int, no obtendrá -1. el molde no es "seguro" ya que los valores se cambian en el camino. –

+0

ah, lo había probado con un compilador diferente. Las advertencias se refieren a "temp <0" cuando la temperatura no está firmada, lo que está bien. Es shou No lanzar en este punto, y no se pierden datos. He probado lo que sugiere, es decir .: int myint (-1); safe_cast (myint, mychar); safe_cast (mychar, myuchar); // Se lanza una excepción aquí safe_cast (myuchar, myint); El elenco de int a char funciona bien. El reparto de char a char sin signo arroja una excepción (como debería). No veo ningún problema aquí. – Tim

0

Debo estar perdiendo algo, pero no es esto lo que quieres ?:

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

No, si utiliza su conversión de -1 (int) a -1 (char) a 0xFF (char sin signo) volver a int, no obtendrá -1. El reparto no es "seguro" ya que los valores cambian en el camino. –

+0

Hola Dribeas, Lo siento, no estoy seguro de lo que dices. . safe_cast (int (-1)) no se desborda, y devuelve bien . safe_cast (char (-1)) cambia el signo (y el valor) y arroja. de ahí el comportamiento correcto. o, ¿qué estás diciendo? – sly

+0

asumiendo que char está firmado, safe_cast (char (-1)) establecerá t en UCHAR_MAX (probablemente 255). luego if (t! = f) promoverá char to int, cediendo a -1 y char sin signo a int, rindiendo 255, por lo tanto, no son iguales. PERO haciendo safe_cast (- 1), establecerá t en UINT_MAX, luego el if no promocionará nada, y convertirá el int en unsigned int (UAC), produciendo nuevamente UINT_MAX, y pensando erróneamente que el lanzamiento tuvo éxito. –

0

que tienen una única cabecera en sweet.hpp llamados conv.hpp. Probará los límites para todos los tipos de enteros y también permitirá y para los moldes de cuerdas para enteros.

short a = to<short>(1337); 
std::string b = to<std::string>(a); 
long c = to<long>(b); 
0

consideran Numerics Caja fuerte en la http://rrsd.com/blincubator.com/bi_library/safe-numerics

Esta biblioteca proporciona reemplazos directos para todos los tipos enteros primitiva C. Las operaciones en C que producen resultados erróneos, incluido el lanzamiento, quedan atrapadas cuando se detectan.

Cuestiones relacionadas