2011-01-25 26 views
12

Hãy nói rằng tôi có một cái gì đó như thế này:Phát hiện nếu đúc một int đến một kết quả enum vào một giá trị không được liệt kê

enum CardColor { HEARTS, DIAMONDS, CLUBS, SPADES}; 

CardColor MyColor = static_cast<CardColor>(100); 

Có (đơn giản) cách để phát hiện, hoặc là tại thời gian biên dịch hoặc tại thời gian chạy, giá trị của MyColor không tương ứng với bất kỳ giá trị được liệt kê nào?

Và tổng quát hơn, nếu các giá trị enum không theo nhau, ví dụ:

enum CardColor { HEARTS = 0, DIAMONDS, CLUBS = 4, SPADES}; 
+2

Khi thời gian chạy hoặc biên dịch? – CashCow

+3

Ngoài "không làm điều đó"? –

+0

@CashCow: tốt, cả hai! Tôi đã cập nhật câu hỏi của mình. –

Trả lời

15

CashCow giới thiệu a decent answer cho câu hỏi này: thật đơn giản để viết một hàm tùy chỉnh để thực hiện một phép chọn đã chọn.

Thật không may, nó cũng có rất nhiều công việc và bạn phải đảm bảo nó được đồng bộ hóa với điều tra để danh sách điều tra trong định nghĩa liệt kê giống như danh sách liệt kê trong hàm đã chọn. Bạn cũng phải viết một trong những điều này cho mỗi điều tra mà bạn muốn để có thể thực hiện một kiểm tra diễn viên.

Thay vì thực hiện tất cả công việc thủ công này, chúng tôi có thể tự động tạo tất cả mã này bằng cách sử dụng bộ tiền xử lý (với một chút trợ giúp từ thư viện bộ xử lý tăng cường). Đây là một macro tạo ra một định nghĩa liệt kê cùng với một hàm checked_enum_cast. Nó có thể là một chút đáng sợ tìm kiếm (macro thế hệ mã thường là khủng khiếp để xem xét), nhưng nó là một kỹ thuật cực kỳ hữu ích để làm quen với.

#include <stdexcept> 
#include <boost/preprocessor.hpp> 

// Internal helper to provide partial specialization for checked_enum_cast 
template <typename Target, typename Source> 
struct checked_enum_cast_impl; 

// Exception thrown by checked_enum_cast on cast failure 
struct invalid_enum_cast : std::out_of_range 
{ 
    invalid_enum_cast(const char* s) 
     : std::out_of_range(s) { } 
}; 

// Checked cast function 
template <typename Target, typename Source> 
Target checked_enum_cast(Source s) 
{ 
    return checked_enum_cast_impl<Target, Source>::do_cast(s); 
} 

// Internal helper to help declare case labels in the checked cast function 
#define X_DEFINE_SAFE_CAST_CASE(r, data, elem) case elem: 

// Macro to define an enum with a checked cast function. name is the name of 
// the enumeration to be defined and enumerators is the preprocessing sequence 
// of enumerators to be defined. See the usage example below. 
#define DEFINE_SAFE_CAST_ENUM(name, enumerators)       \ 
    enum name                \ 
    {                  \ 
     BOOST_PP_SEQ_ENUM(enumerators)          \ 
    };                  \ 
                      \ 
    template <typename Source>            \ 
    struct checked_enum_cast_impl<name, Source>       \ 
    {                  \ 
     static name do_cast(Source s)          \ 
     {                 \ 
      switch (s)              \ 
      {                \ 
      BOOST_PP_SEQ_FOR_EACH(X_DEFINE_SAFE_CAST_CASE, 0, enumerators) \ 
       return static_cast<name>(s);        \ 
      default:              \ 
       throw invalid_enum_cast(BOOST_PP_STRINGIZE(name));   \ 
      }                \ 
      return name();             \ 
     }                 \ 
    }; 

Đây là cách bạn sẽ sử dụng điều đó với CardColor ví dụ của bạn:

DEFINE_SAFE_CAST_ENUM(CardColor, (HEARTS) (CLUBS) (SPADES) (DIAMONDS)) 

int main() 
{ 
    checked_enum_cast<CardColor>(1); // ok 
    checked_enum_cast<CardColor>(400); // o noez! an exception! 
} 

Dòng đầu tiên thay thế định nghĩa enum CardColor ... của bạn; nó xác định việc liệt kê và cung cấp một chuyên môn cho phép bạn sử dụng checked_enum_cast để truyền các số nguyên đến CardColor.

Điều này có thể trông giống như rất nhiều rắc rối chỉ để có được một chức năng đúc kiểm tra cho enums của bạn, nhưng kỹ thuật này là rất hữu ích và mở rộng. Bạn có thể thêm các chức năng làm đủ mọi thứ. Ví dụ, tôi có một hàm tạo các hàm để chuyển đổi các kiểu liệt kê thành và từ các biểu diễn chuỗi và các hàm thực hiện một số chuyển đổi và kiểm tra khác mà tôi sử dụng cho hầu hết các liệt kê của mình.

Hãy nhớ rằng, bạn phải viết và gỡ lỗi macro lớn, xấu xí này một lần, sau đó bạn có thể sử dụng nó ở mọi nơi.

+4

+1 để sử dụng "o noez" –

3

Nó được phổ biến để có một yếu tố bổ sung ở phần cuối của enum cho thấy số lượng các mục trong đó. Bạn có thể sử dụng giá trị đó để kiểm tra thời gian chạy nếu giá trị hợp lệ:

enum CardColor { HEARTS, DIAMONDS, CLUBS, SPADES, CARDS_COUNT}; 

CardColor MyColor = static_cast<CardColor>(100); 

if (MyColor >= CARDS_COUNT) { 
    /* Invalid value */ 
} 
+3

Không, không. Bạn có thực sự muốn một CardColor chấp nhận được CARDS_COUNT không? – wheaties

+5

@wheaties Trên thực tế, bất kỳ số nguyên nào là một CardColor có thể chấp nhận được. Đó là lý do tại sao câu hỏi này được hỏi ngay từ đầu. – vz0

+1

Tôi nghĩ rằng một vấn đề là 'static_cast (100); 'gây nên hành vi không xác định, và điều đó có nghĩa là trong lý thuyết giá trị trả về thậm chí có thể là HEARTS hoặc DIAMONDS. Xem http://stackoverflow.com/a/33608071/2436175 – Antonio

1

Lúc đầu - không nên làm điều này.

Nhưng nếu bạn muốn, tôi đề nghị để hardcode giá trị nguyên liệt kê:

enum CardColor { HEARTS = 10, DIAMONDS = 11, CLUBS = 12, SPADES = 13}; 

Sau đó, quá tải toán tử assigment:

CardColor operator = (int value) 
{ 
    switch (value) 
    { 
     case 10: 
      return HEARTS; 
     // case for other values 
     default: 
      // throw an exception or something 
    } 
} 
+0

trong khi kỹ thuật này làm những gì OP muốn, nó cũng hoàn toàn vô hiệu hóa điểm của việc sử dụng một enum. @ CashCow của câu trả lời là tương tự, nhưng tốt hơn nhiều. – tenfour

8

đơn giản thời gian chạy giải pháp sẽ không sử dụng static_cast nhưng việc sử dụng một chức năng kiểm tra cho bạn. Nếu bạn đặt enum của bạn bên trong một lớp học, bạn có thể làm điều đó thông qua lớp học. Một cái gì đó như:

class CardCheck 
{ 
public: 
    enum CardColor { HEARTS, DIAMONDS, CLUBS, SPADES }; 

    explicit CardCheck(int x) : c(static_cast<CardColor>(x)) 
    { 
    switch(c) 
    { 
     case HEARTS: case DIAMONDS: case CLUBS: case SPADES: 
      break; 

     default: 
     // assert or throw 
    } 
    } 

    CardColor get() const 
    { 
    return c; 
    } 

private: 
    CardColor c;  
}; 
+1

Diễn viên có thể diễn ra sau khi kiểm tra: nó có khả năng có thể dẫn đến tràn (tôi nghĩ). –

+2

Điều thú vị là chúng tôi đã làm một cái gì đó như thế này trong mã của chúng tôi để xác nhận các số nguyên được cast vào enums. Một trình biên dịch đã quyết định tối ưu hóa câu lệnh switch, có lẽ vì tất cả các trường hợp có thể được bảo vệ nên nó giả định nó luôn hợp lệ. Có lẽ lý do chính đáng để bao gồm CARD_COUNT. Sau đó, có ít nhất một giá trị xấu có thể và trình biên dịch sẽ không tối ưu hóa. – CashCow

0

Giá trị liệt kê có thể chồng chéo hoặc có lỗ; ngoài ra, các biến thực tế có thể được gán 0, bất kỳ giá trị nào từ tập hợp hoặc giá trị OR của giá trị được phép. Vì vậy:

enum suites { hearts, diamonds, clubs, spades }; 

cho phép các giá trị 0, 1, 2, 3;

enum suites { hearts = 1 << 0, diamonds = 1 << 1, clubs = 1 << 2, spades = 1 << 4 }; 

cho phép bất kỳ giá trị từ 0 đến 15.

Nếu bạn sử dụng một enum để xác định giá trị bit, nó thường là một ý tưởng tốt để xác định (nhị phân) operator&, operator|, operator&=operator|=. Nếu bạn không làm điều đó, bạn sẽ cần một diễn viên rõ ràng bất cứ khi nào một giá trị không có trong tập hợp được tạo ra, do đó, những nơi mà điều này xảy ra có thể dễ dàng phát hiện. Có các trình biên dịch có thể cảnh báo nếu một số nằm ngoài phạm vi cho phép được gán hoặc không có tên nào, tên đầu tiên hoặc tất cả tên có trình khởi tạo được đính kèm (đây là vi phạm quy tắc MISRA-C).

+0

để joker hoặc là 0 (không phù hợp) hoặc 15 (tất cả 4 người trong số họ)? – CashCow

+0

Có. –

4

clang có hỗ trợ kiểm tra tràn động. Xem công tắc -fsanitize=enum. Một chương trình được biên dịch với công tắc này sẽ báo hiệu lỗi gán enum thông qua đầu ra stderr. Điều này sẽ cho phép bạn thực hiện các kiểm tra gỡ lỗi. Nó không thích hợp để kiểm tra đầu vào đáng ngờ trong xây dựng chính thức.

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