2009-09-07 36 views
37

Trong C++, có thể liệt kê trên một enum (thời gian chạy hoặc biên dịch (ưa thích)) và các hàm gọi/tạo mã cho mỗi lần lặp không?Liệt kê trên một enum trong C++

trường hợp sử dụng

mẫu:

enum abc 
{  
    start 
    a, 
    b, 
    c, 
    end 
}  
for each (__enum__member__ in abc) 
{  
    function_call(__enum__member__);  
} 

bản sao chính đáng:

+0

Làm cách nào để bạn chọn chức năng cho cuộc gọi? Bạn có thể đăng một số mã giả làm thế nào để bạn giả sử để làm điều đó? Nó có thể giúp chúng tôi giúp bạn. –

+0

được cập nhật cho krill – jameszhao00

+0

Để xem thời gian chạy tại http://stackoverflow.com/questions/1292426/is-there-any-well-known-paradigm-for-iterating-through-enum. (Nếu không có thời gian biên dịch, câu hỏi của bạn sẽ là bản sao chính xác của nó.) – sbi

Trả lời

54

Để thêm vào @StackedCrooked câu trả lời, bạn có thể quá tải operator++, operator--operator* và có iterator như chức năng.

enum Color { 
    Color_Begin, 
    Color_Red = Color_Begin, 
    Color_Orange, 
    Color_Yellow, 
    Color_Green, 
    Color_Blue, 
    Color_Indigo, 
    Color_Violet, 
    Color_End 
}; 

namespace std { 
template<> 
struct iterator_traits<Color> { 
    typedef Color value_type; 
    typedef int difference_type; 
    typedef Color *pointer; 
    typedef Color &reference; 
    typedef std::bidirectional_iterator_tag 
    iterator_category; 
}; 
} 

Color &operator++(Color &c) { 
    assert(c != Color_End); 
    c = static_cast<Color>(c + 1); 
    return c; 
} 

Color operator++(Color &c, int) { 
    assert(c != Color_End); 
    ++c; 
    return static_cast<Color>(c - 1); 
} 

Color &operator--(Color &c) { 
    assert(c != Color_Begin); 
    return c = static_cast<Color>(c - 1); 
} 

Color operator--(Color &c, int) { 
    assert(c != Color_Begin); 
    --c; 
    return static_cast<Color>(c + 1); 
} 

Color operator*(Color c) { 
    assert(c != Color_End); 
    return c; 
} 

Hãy thử nghiệm với một số <algorithm> mẫu

void print(Color c) { 
    std::cout << c << std::endl; 
} 

int main() { 
    std::for_each(Color_Begin, Color_End, &print); 
} 

Bây giờ, Color là một iterator hai chiều liên tục. Đây là một lớp tôi có thể tái sử dụng được mã hóa trong khi thực hiện thủ công ở trên. Tôi nhận thấy nó có thể làm việc cho nhiều enums hơn, do đó, lặp lại cùng một mã một lần nữa là khá tẻ nhạt

// Code for testing enum_iterator 
// -------------------------------- 

namespace color_test { 
enum Color { 
    Color_Begin, 
    Color_Red = Color_Begin, 
    Color_Orange, 
    Color_Yellow, 
    Color_Green, 
    Color_Blue, 
    Color_Indigo, 
    Color_Violet, 
    Color_End 
}; 

Color begin(enum_identity<Color>) { 
    return Color_Begin; 
} 

Color end(enum_identity<Color>) { 
    return Color_End; 
} 
} 

void print(color_test::Color c) { 
    std::cout << c << std::endl; 
} 

int main() { 
    enum_iterator<color_test::Color> b = color_test::Color_Begin, e; 
    while(b != e) 
    print(*b++); 
} 

Thực hiện sau.

template<typename T> 
struct enum_identity { 
    typedef T type; 
}; 

namespace details { 
void begin(); 
void end(); 
} 

template<typename Enum> 
struct enum_iterator 
    : std::iterator<std::bidirectional_iterator_tag, 
        Enum> { 
    enum_iterator():c(end()) { } 

    enum_iterator(Enum c):c(c) { 
    assert(c >= begin() && c <= end()); 
    } 

    enum_iterator &operator=(Enum c) { 
    assert(c >= begin() && c <= end()); 
    this->c = c; 
    return *this; 
    } 

    static Enum begin() { 
    using details::begin; // re-enable ADL 
    return begin(enum_identity<Enum>()); 
    } 

    static Enum end() { 
    using details::end; // re-enable ADL 
    return end(enum_identity<Enum>()); 
    } 

    enum_iterator &operator++() { 
    assert(c != end() && "incrementing past end?"); 
    c = static_cast<Enum>(c + 1); 
    return *this; 
    } 

    enum_iterator operator++(int) { 
    assert(c != end() && "incrementing past end?"); 
    enum_iterator cpy(*this); 
    ++*this; 
    return cpy; 
    } 

    enum_iterator &operator--() { 
    assert(c != begin() && "decrementing beyond begin?"); 
    c = static_cast<Enum>(c - 1); 
    return *this; 
    } 

    enum_iterator operator--(int) { 
    assert(c != begin() && "decrementing beyond begin?"); 
    enum_iterator cpy(*this); 
    --*this; 
    return cpy; 
    } 

    Enum operator*() { 
    assert(c != end() && "cannot dereference end iterator"); 
    return c; 
    } 

    Enum get_enum() const { 
    return c; 
    } 

private: 
    Enum c; 
}; 

template<typename Enum> 
bool operator==(enum_iterator<Enum> e1, enum_iterator<Enum> e2) { 
    return e1.get_enum() == e2.get_enum(); 
} 

template<typename Enum> 
bool operator!=(enum_iterator<Enum> e1, enum_iterator<Enum> e2) { 
    return !(e1 == e2); 
} 
+3

+1: Điều này thực sự thanh lịch. – ereOn

4

Không phải là có thể mà không một littl e lao động thủ công. Rất nhiều công việc có thể được thực hiện bằng các macro, nếu bạn sẵn sàng nghiên cứu khu vực đó.

1

Không

Tuy nhiên, bạn có thể xác định lớp của riêng bạn thực hiện các tính năng giống như lặp lại. Bạn có thể nhớ lại một mẹo từ 1,5 ngày Java trước, được gọi là "kiểu thiết kế enum an toàn". Bạn có thể làm tương đương C++.

2

Mở rộng trên những gì Konrad nói, một trong những thành ngữ có thể trong trường hợp của "tạo mã cho mỗi lần lặp" là sử dụng một tập tin bao gồm đại diện liệt kê:

mystuff.h:

#ifndef LAST_ENUM_ELEMENT 
#define LAST_ENUM_ELEMENT(ARG) ENUM_ELEMENT(ARG) 
#endif 

ENUM_ELEMENT(foo) 
ENUM_ELEMENT(bar) 
LAST_ENUM_ELEMENT(baz) 

// not essential, but most likely every "caller" should do it anyway... 
#undef LAST_ENUM_ELEMENT 
#undef ENUM_ELEMENT 

enum.h:

// include guard goes here (but mystuff.h doesn't have one) 

enum element { 
    #define ENUM_ELEMENT(ARG) ARG, 
    #define LAST_ENUM_ELEMENT(ARG) ARG 
    #include "mystuff.h" 
} 

main.cpp:

#include "enum.h" 
#define ENUM_ELEMENT(ARG) void do_##ARG(); 
#include "mystuff.h" 

element value = getValue(); 
switch(value) { 
    #define ENUM_ELEMENT(ARG) case ARG: do_##ARG(); break; 
    #include "mystuff.h" 
    default: std::terminate(); 
} 

Vì vậy, để thêm phần tử mới "qux", bạn thêm nó vào mystuff.h và viết hàm do_qux. Bạn không phải chạm vào mã công văn.

Tất nhiên nếu các giá trị trong enum của bạn cần phải là số nguyên không liên tiếp cụ thể, thì bạn sẽ kết thúc việc duy trì định nghĩa enum và danh sách ENUM_ELEMENT(foo) ... riêng, rất lộn xộn.

1

Điều này có vẻ hacky với tôi, nhưng có thể phù hợp với mục đích của bạn:

enum Blah { 
    FOO, 
    BAR, 
    NUM_BLAHS 
}; 

// later on 
for (int i = 0; i < NUM_BLAHS; ++i) { 
    switch (i) { 
    case FOO: 
    // foo stuff 
    break; 
    case BAR: 
    // bar stuff 
    break; 
    default: 
    // you're missing a case statement 
    } 
} 

Nếu bạn cần một giá trị khởi đầu đặc biệt, bạn có thể làm điều đó một hằng số và đặt nó trong enum của bạn. Tôi đã không kiểm tra nếu biên dịch này, nhưng nó phải được gần như có :-). Hi vọng điêu nay co ich.

Tôi nghĩ phương pháp này có thể là một sự cân bằng tốt cho trường hợp sử dụng của bạn. Sử dụng nó nếu bạn không cần phải làm điều này cho một loạt các loại liệt kê khác nhau và bạn không muốn đối phó với các công cụ tiền xử lý. Chỉ cần chắc chắn rằng bạn nhận xét và có thể thêm một TODO để thay đổi nó vào một ngày sau đó để một cái gì đó tốt hơn :-).

+4

'for' và' 'switch' hoàn toàn vô dụng/vô nghĩa vì bạn vẫn sử dụng từng giá trị liên tiếp. Chỉ cần bỏ qua cả hai và thực thi 'foo stuff', theo sau là' bar stuff' vv. –

+0

Điều này chỉ hữu ích trong một chương trình thử nghiệm nơi bạn chạy một loạt các bài kiểm tra. Các điều kiện bắt đầu/kết thúc sẽ là các biến vì vậy nếu bạn chỉ muốn chạy một thử nghiệm bạn đặt start = end: for (int i = start; i <= end; ++ i) {....} Trong tất cả các trường hợp khác, Tôi đồng ý với Konrad: chuyển đổi bên trong một cho có lẽ là một thiết kế xấu. – jmucchiello

+2

Vòng lặp for để nhấn từng trường hợp của câu lệnh switch? Xin vui lòng. Đây là Stackoverflow, không phải thedailywtf.com. Đó chỉ là một cách không cần thiết phức tạp để thực hiện "các công cụ foo", sau đó "công cụ thanh". Điều đó không yêu cầu bất kỳ loại điều khiển luồng nào. – Alan

42

C++ hiện không cung cấp tính lặp lại của điều tra viên. Mặc dù vậy, đôi khi nhu cầu phát sinh cho điều này. Cách giải quyết phổ biến là thêm các giá trị đánh dấu điểm bắt đầu và kết thúc.Ví dụ:

enum Color 
{ 
    Color_Begin, 
    Color_Red = Color_Begin, 
    Color_Orange, 
    Color_Yellow, 
    Color_Green, 
    Color_Blue, 
    Color_Indigo, 
    Color_Violet, 
    Color_End 
}; 

void foo(Color c) 
{ 
} 


void iterateColors() 
{ 
    for (size_t colorIdx = Color_Begin; colorIdx != Color_End; ++colorIdx) 
    { 
     foo(static_cast<Color>(colorIdx)); 
    } 
} 
0

Bạn có thể thực hiện một số kỹ thuật thời gian chạy được đề xuất tĩnh với TMP.

#include <iostream> 

enum abc 
{ 
    a, 
    b, 
    c, 
    end 
}; 

void function_call(abc val) 
{ 
    std::cout << val << std::endl; 
} 

template<abc val> 
struct iterator_t 
{ 
    static void run() 
    { 
     function_call(val); 

     iterator_t<static_cast<abc>(val + 1)>::run(); 
    } 
}; 

template<> 
struct iterator_t<end> 
{ 
    static void run() 
    { 
    } 
}; 

int main() 
{ 
    iterator_t<a>::run(); 

    return 0; 
} 

Kết quả của chương trình này là:

0 
1 
2 

Xem Ch 1 của Abrahams, Gurtovoy "C++ Template Metaprogramming" cho một điều trị tốt của kỹ thuật này. Ưu điểm để làm nó theo cách này trong kỹ thuật runtime đề xuất là, khi bạn tối ưu hóa mã này, nó có thể inline tĩnh học và tương đương với:

function_call(a); 
function_call(b); 
function_call(c); 

Inline FUNCTION_CALL để được giúp đỡ nhiều hơn từ các trình biên dịch.

Những lời chỉ trích tương tự về các kỹ thuật lặp lại đếm ngược khác được áp dụng tại đây. Kỹ thuật này chỉ hoạt động nếu liệt kê của bạn tăng liên tục từ một kết thúc thông qua bởi những người thân.

1

Tôi thường làm điều đó như thế này:

enum abc 
{  
    abc_begin, 
    a = abc_begin, 
    b, 
    c, 
    abc_end 
}; 

void foo() 
{ 
    for(auto&& r : range(abc_begin,abc_end)) 
    { 
     cout << r; 
    } 
} 


range là hoàn toàn chung chung, và được xác định như sau:

template <typename T> 
class Range 
{ 
public: 
    Range(const T& beg, const T& end) : b(beg), e(end) {} 
    struct iterator 
    { 
     T val; 
     T operator*() { return val; } 
     iterator& operator++() { val = (T)(1+val); return *this; } 
     bool operator!=(const iterator& i2) { return val != i2.val; } 
    }; 
    iterator begin() const { return{b}; } 
    iterator end() const { return{e}; } 
private: 
    const T& b; 
    const T& e; 
}; 

template <typename T> 
Range<T> range(const T& beg, const T& end) { return Range<T>(beg,end); } 
0

Tình yêu khuôn mẫu nhưng tôi sẽ làm nốt những điều này cho tôi tương lai/sử dụng của người khác vì vậy chúng tôi không bị mất với bất kỳ điều nào ở trên.

Enums rất thuận tiện để so sánh mọi thứ theo kiểu được đặt hàng đã biết. Chúng thường được mã hóa cứng thành các hàm vì lợi ích của khả năng đọc so với các giá trị số nguyên. Hơi giống với định nghĩa tiền xử lý, ngoại trừ việc chúng không được thay thế bằng chữ, nhưng được lưu giữ và truy cập trong thời gian chạy.

Nếu chúng ta đã có một enum xác định mã lỗi html và chúng tôi biết rằng mã lỗi trong 500s lỗi máy chủ, nó có thể là đẹp hơn để đọc một cái gì đó như:

enum HtmlCodes {CONTINUE_CODE=100,CLIENT_ERROR=400,SERVER_ERROR=500,NON_STANDARD=600}; 

if(errorCode >= SERVER_ERROR && errorCode < NON_STANDARD) 

hơn

if(errorCode >= 500 && errorCode < 600) 

Phần quan trọng là điều này, chúng tương tự như mảng! Nhưng được sử dụng đểtruyềngiá trị số nguyên.

ngắn ví dụ:

enum Suit {Diamonds, Hearts, Clubs, Spades}; 
//does something with values in the enum past "Hearts" in this case 
for(int i=0;i<4;i++){ 
    //Could also use i or Hearts, because the enum will turns these both back into an int 
    if((Suit)(i) > 1) 
    { 
     //Whatever we'd like to do with (Suit)(i) 
    } 
} 

Thông thường enums cũng được sử dụng với char * mảng hoặc mảng chuỗi để bạn có thể in một số thông điệp với giá trị đi kèm. Thông thường họ chỉ mảng với cùng một tập hợp các giá trị trong enum, như vậy:

char* Suits[4] = {"Diamonds", "Hearts", "Clubs", "Spades"}; 
//Getting a little redundant 
cout << Suits[Clubs] << endl; 
//We might want to add this to the above 
//cout << Suits[(Suit)(i)] << endl; 

Và tất nhiên nó thậm chí còn đẹp hơn để tạo ra một lớp chung mà xử lý lặp đi lặp lại cho enums như câu trả lời ở trên.

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