2009-06-15 27 views
8

Tôi đang cố gắng viết một hàm mẫu C++ sẽ ném một ngoại lệ thời gian chạy vào số nguyên tràn trong phôi giữa các loại tích phân khác nhau, với độ rộng khác nhau và có thể không khớp/không khớp. Đối với những mục đích này, tôi không quan tâm đến việc truyền từ các loại dấu phẩy động đến các loại tích phân, cũng như các chuyển đổi đối tượng-đối tượng khác. Tôi muốn làm điều này mà không cần phải viết nhiều mã trường hợp đặc biệt. Đây là những gì tôi hiện có:C++ Mẫu cho các số nguyên an toàn

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

Điều này có chính xác và hiệu quả không?

EDIT: Vì nhiều lý do stl không có sẵn, vì vậy tôi không thể sử dụng std :: numeric_limits và mọi thứ từ Boost đều đúng.

+0

Nhưng bạn có thể sao chép mã bạn cần từ numeric_limits vào trình trợ giúp được tạo khuôn mẫu của riêng bạn. Chỉ định mọi thứ cho uint64 (hoặc bất kỳ kích thước tối đa cho phép nào) và so sánh trong loại đó. –

+1

Điều đó có thể hoạt động, nhưng thực sự cần phải biết về các điều khoản cấp phép khi sao chép mã như thế này. Bên cạnh khả năng vi phạm các điều khoản, người ta có thể vô ý "lây nhiễm" mã của họ, như trường hợp với GPL. Đảm bảo cả hai giấy phép đều tương thích trước khi thực hiện loại điều này. Thông báo từ chối "Tôi không phải là luật sư" thông thường được áp dụng. – Void

+0

Các lý do khác nhau mà bạn không thể sử dụng STL là gì? – GManNickG

Trả lời

5

Bạn đã thử SafeInt chưa? Đó là một mẫu nền tảng chéo sẽ thực hiện kiểm tra tràn số nguyên cho một loạt các loại số nguyên. Nó có sẵn trên CodePlex

12

Bạn có thể nhận được các giá trị an toàn tối thiểu và tối đa (và rất nhiều thông tin khác) cho bất kỳ loại cơ bản nào theo cách thanh lịch hơn bằng cách sử dụng mẫu std::numeric_limits, ví dụ: std::numeric_limits<T>::max(). Bạn sẽ cần phải bao gồm <limits>.

Tham chiếu: http://www.cplusplus.com/reference/std/limits/numeric_limits/

+0

chỉnh sửa này 7 năm sau đó đã làm cho tôi thay đổi ý kiến ​​của tôi thành một downvote. Bởi vì ví dụ được trình bày thêm bởi @ jpo38 không hoạt động. Ví dụ: Từ = int, To = unsigned. nguồn == - 1. –

+0

Thực ra, tôi đã thấy và sửa mã đó trong mã của mình kể từ khi tôi chỉnh sửa bài đăng này. Nhưng quên cập nhật chỉnh sửa ... xin lỗi. Bây giờ tôi ném nếu 'static_cast (static_cast (nguồn))! = Source' (về cơ bản, nếu một số thông tin bị mất bởi dàn diễn viên ... điều này thật tuyệt vời. Một số cách tương tự như những gì Tim đề xuất dưới đây. Không tham chiếu đến max/min – jpo38

11

Tăng cường tùy chọn? Nếu có, hãy thử boost::numeric_cast<>. Nó xuất hiện để cung cấp các đặc tính bạn đang tìm kiếm.

1

Tôi thích hợp trong giả định rằng trong trường hợp đó R được ký bạn đang cố gắng để lấp đầy Rmax với tất cả 1s trừ các bit cuối cùng? Nếu đúng như vậy, bạn nên có 0x80 (1000 0000) thay vì 0x10 (0001 0000).

Ngoài ra, có vẻ như chức năng của bạn không hỗ trợ số âm cho nguồn.

Edit:

Đây là một phiên bản hơi thay đổi nội dung mà tôi đã thử nghiệm để chuyển đổi từ ints để chars:

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

Chỉnh sửa: Lỗi cố định.

7

Tôi nghĩ rằng những công việc này ngay bây giờ, bất kể bạn có sử dụng phần bổ sung của hai hay không. Vui lòng kiểm tra rộng rãi trước khi bạn sử dụng nó. Họ đưa ra kết quả sau. Mỗi dòng đưa ra một xác nhận không thành công (chỉ cần thay đổi chúng thành các ngoại lệ như bạn vui lòng)

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

Thực hiện. Đầu tiên một số tiện ích để kiểm tra các xếp hạng số nguyên (các loại có thứ hạng cao hơn sẽ có thể chứa các giá trị của các loại có thứ hạng thấp hơn, cùng một dấu hiệu. Và một số công cụ quảng cáo, để có thể tìm ra một loại an toàn chung (điều này sẽ không bao giờ mang lại một loại ký nếu một kiểu unsigned có liên quan, nếu loại ký sẽ không thể lưu trữ tất cả các giá trị của một unsigned).

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

các mẫu chuyển đổi sử dụng trong số họ, để tìm ra cho mỗi trường hợp cần phải làm gì hoặc không làm gì.

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

Làm thế nào về:

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

Sau đó, bạn chỉ cần kiểm tra nếu đúc làm việc. Hãy chắc chắn rằng bạn lấy lại những gì bạn bắt đầu với, và rằng các dấu hiệu không lật.

EDIT: Kể từ khi bình luận dưới đây đã sai lầm, ở đây nó được, định dạng:

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

Dàn diễn viên từ int char hoạt động tốt. Các diễn viên từ char để unsigned char ném một ngoại lệ (như nó nên). Tôi không thấy vấn đề ở đây.

+0

o, Trình biên dịch sẽ nhổ cảnh báo khi một trong hai loại không được ký và khác không phải là (so sánh luôn sai do phạm vi giới hạn của kiểu dữ liệu trong g ++) khi so sánh với 0, nhưng nó sẽ không ném và dữ liệu sẽ bị mất: Nếu bạn sử dụng diễn viên của bạn từ -1 (int) đến -1 (char) đến 0xFF (unsigned char) trở lại int bạn sẽ không nhận được -1. –

+0

ah, tôi đã thử nghiệm với một trình biên dịch khác nhau Các cảnh báo đề cập đến "temp <0" khi temp chưa được ký, điều này là ok. Nó shou không ném vào thời điểm này, và không có dữ liệu bị mất. Tôi đã thử nghiệm những gì bạn đề xuất, ví dụ: int myint (-1); safe_cast (myint, mychar); safe_cast (mychar, myuchar); // Ngoại lệ được ném ở đây safe_cast (myuchar, myint); Các diễn viên từ int đến char hoạt động tốt. Các diễn viên từ char để unsigned char ném một ngoại lệ (như nó nên). Tôi không thấy sự cố ở đây. – Tim

0

tôi phải mất một cái gì đó, nhưng không phải là những gì bạn muốn ?:

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

Không, nếu bạn sử dụng dàn diễn viên của bạn từ -1 (int) đến -1 (char) đến 0xFF (unsigned char) trở lại int bạn sẽ không nhận được -1. Dàn diễn viên không an toàn vì các giá trị được thay đổi theo cách. –

+0

Xin chào Dribeas, Tôi xin lỗi, tôi không chắc bạn đang nói gì. . safe_cast (int (-1)) không tràn, và trả về tiền phạt . safe_cast (char (-1)) thay đổi dấu (và giá trị), và ném. do đó có hành vi đúng. hoặc, bạn đang nói gì vậy? – sly

+0

giả định char được ký, safe_cast (char (-1)) sẽ đặt t thành UCHAR_MAX (có thể là 255). sau đó nếu (t! = f) sẽ thúc đẩy char đến int, năng suất -1 và unsigned char để int, năng suất 255, do đó chúng không bằng nhau. NHƯNG thực hiện safe_cast (- 1), nó sẽ đặt t thành UINT_MAX, sau đó if sẽ không quảng bá gì, và chuyển int sang unsigned int (UAC), do đó sinh ra UINT_MAX một lần nữa, và suy nghĩ sai về việc cast thành công. –

0

Tôi có một tiêu đề duy nhất tại sweet.hpp gọi conv.hpp. Nó sẽ kiểm tra giới hạn cho tất cả các loại số nguyên và cũng cho phép và từ chuỗi phôi cho số nguyên.

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

xem xét numerics an toàn tại http://rrsd.com/blincubator.com/bi_library/safe-numerics

Thư viện này cung cấp thả vào thay thế cho tất cả các loại nguyên nguyên thủy C. Các hoạt động C dẫn đến kết quả sai - bao gồm cả việc đúc bị kẹt khi được phát hiện.

Các vấn đề liên quan