2009-06-15 4 views
8

Próbuję napisać funkcję szablonu C++, która wyrzuci wyjątek czasu wykonywania na przepełnienie liczby całkowitej w rzutach między różnymi typami całek, o różnych szerokościach i możliwym niedopasowaniu podpisanym/unsigned. W tym celu nie chodzi mi o rzutowanie z typów zmiennoprzecinkowych na typy całkowite ani inne konwersje obiekt-obiekt. Chciałbym to zrobić bez konieczności pisania wielu specjalnych kodów przypadków. Oto, co aktualnie mam:C++ Szablon dla bezpiecznych rzutów całkowitych

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

Czy to jest poprawne i wydajne?

EDYCJA: Z różnych powodów stl nie jest dostępny, więc nie mogę używać std :: numeric_limits, a wszystko z Boost jest już na miejscu.

+0

Ale można skopiować kod z numeric_limits do własnego szablonu pomocy. Przypisz wszystko do uint64 (lub cokolwiek, co jest maksymalnym dozwolonym rozmiarem) i wykonaj porównania w tym typie. –

+1

To może zadziałać, ale trzeba naprawdę znać warunki licencji podczas kopiowania takiego kodu. Oprócz potencjalnego naruszenia warunków, można nieumyślnie "zainfekować" ich kod, tak jak w przypadku GPL. Upewnij się, że obie licencje są kompatybilne przed zrobieniem tego typu rzeczy. Obowiązuje zwykle wyłączenie odpowiedzialności "Nie jestem prawnikiem". – Void

+0

Z jakich powodów nie możesz korzystać ze STL? – GManNickG

Odpowiedz

5

Czy próbowałeś SafeInt? Jest to szablon wieloplatformowy, który przeprowadza kontrole przekroczenia liczby całkowitej dla różnych typów liczb całkowitych. Jest ona dostępna na CodePlex

12

Możesz uzyskać minimalne i maksymalne bezpieczne wartości (i wiele innych informacji) dla dowolnego typu podstawowego w znacznie bardziej elegancki sposób, korzystając z szablonu std::numeric_limits, np. std::numeric_limits<T>::max(). Musisz dołączyć <limits>.

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

+0

ta edycja 7 lat później zmusiła mnie do zmiany mojego uprowadzenia na głos. Ponieważ przedstawiony przykład dodany przez @ jpo38 nie działa. Przykładowa para: From = int, To = unsigned. źródło == - 1. –

+0

Właściwie to widziałem i naprawiłem to w moim kodzie, odkąd zredagowałem ten post. Ale zapomniałem zaktualizować edycję ... przepraszam. Teraz rzucam, jeśli 'static_cast (static_cast (źródło))! = Source' (w zasadzie, jeśli niektóre informacje zostały utracone przez obsadę ... działa to świetnie, w jakiś sposób podobne do tego, co Tim proponuje poniżej. Brak odniesienia do max/min życzenie kończy się niepowodzeniem podczas przechodzenia z podpisu na niepodpisany i na odwrót: – jpo38

1

mam rację zakładając, że w przypadku, gdy R jest podpisany staramy się wypełnić RMAX ze wszystkimi 1s wyjątkiem ostatniego kawałka? Jeśli tak jest, powinieneś mieć 0x80 (1000 0000) zamiast 0x10 (0001 0000).

Również nie wygląda na to, że twoja funkcja obsługuje liczby ujemne dla źródła.

Edit:

Oto nieco edytowany wersja Przetestowałem do konwersji z int do znaków:

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

Edit: naprawiono błąd.

7

Myślę, że to działa teraz, niezależnie od tego, czy używasz dopełnienia dwóch, czy nie. Przed użyciem go należy dokładnie przetestować. Dają następujące wyniki. Każda linia daje jedną asercję (po prostu zmień ją na wyjątki, jak chcesz).

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

Realizacja. Najpierw niektóre narzędzia do sprawdzania liczb całkowitych (typy z wyższymi rangami będą mogły zawierać wartości typów o niższej randze, z tym samym znakiem, oraz niektóre narzędzia promocyjne, aby móc znaleźć typowy, bezpieczny typ (to nigdy nie będzie otrzymując podpisaną typ jeśli typ unsigned jest zaangażowany, gdy podpisał typ nie będzie w stanie przechowywać wszystkie wartości unsigned jeden).

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

szablonów konwersji z nich korzystać, aby dowiedzieć się, dla każdego kiedy to, co należy zrobić, a czego nie.

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

Jak o:

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

to jesteś po prostu sprawdzenie, czy odlewanie pracował. Upewnij się, że odzyskałeś to, co zacząłeś i że znak się nie odwrócił.

EDIT: Ponieważ komentarz poniżej mnie zawiedli, to jest tutaj, sformatowane:

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

Obsada z int na char działa dobrze. Rzucanie z char na unsigned char powoduje wyjątek (tak jak powinien). Nie widzę tutaj problemu.

+0

o, Kompilator wypluje ostrzeżenia, gdy jeden z tych dwóch typów jest niepodpisany, a drugi nie (porównanie zawsze jest fałszywe z powodu ograniczonego zakresu typu danych wg g ++), gdy w porównaniu z 0, ale nie będzie rzutować, a dane zostaną utracone: Jeśli użyjesz rzutowania od -1 (int) do -1 (char) do 0xFF (unsigned char) z powrotem do int, nie otrzymasz -1. cast nie jest "bezpieczny", ponieważ wartości zmieniają się w ten sposób –

+0

ah, testowałem z innym kompilatorem Ostrzeżenia odnoszą się do "temp <0", gdy temp jest niepodpisany, co jest ok. To jest w porządku. W tym momencie nie rzucać, a żadne dane nie są tracone. Testowałem to, co sugerujesz, tj .: int myint (-1); safe_cast (myint, mychar); safe_cast (mychar, myuchar); // Wyjątek jest wyrzucany tutaj safe_cast (myuchar, myint); Rzucanie od int do char działa dobrze. Rzucanie z char na unsigned char powoduje wyjątek (tak jak powinien). Nie widzę tutaj problemu. – Tim

0

I musi być brakuje czegoś, ale nie jest to to, co chcesz ?:

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

Nie, jeśli użyjesz rzutowania od -1 (int) do -1 (char) do 0xFF (unsigned char) z powrotem do int, nie otrzymasz -1. Obsada nie jest "bezpieczna", ponieważ wartości się zmieniają. –

+0

Cześć Dribeas, Przykro mi, nie jestem pewien, co mówisz. . safe_cast (int (-1)) nie przepełnia i zwraca wartość . safe_cast (char (-1)) zmienia znak (i ​​wartość) i rzuca. stąd prawidłowe zachowanie. lub, co ty mówisz? – sly

+0

zakładając, że char jest podpisany, safe_cast (char (-1)) ustawi t na UCHAR_MAX (prawdopodobnie 255). wtedy jeśli (t! = f) będzie promować char do int, dając -1 i unsigned char do int, dając 255, a więc nie są równe. ALE wykonanie safe_cast (- 1), ustawi to na UINT_MAX, wtedy if nie będzie promowało niczego, i konwertuje int na unsigned int (UAC), tym samym ponownie dając UINT_MAX, i błędnie myśląc, że rzutowanie się powiedzie. –

0

mam jeden nagłówek na sweet.hpp nazwie conv.hpp. Sprawdza granice dla wszystkich typów liczb całkowitych, a także pozwala na i od ciągów rzutów dla liczby całkowitej.

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