Iterating over non-incremental Enum

前端 未结 15 1705
长发绾君心
长发绾君心 2021-01-31 15:42

Before you ask, I\'ve looked and looked for this on SO, and cannot find a solid answer.

I need to be able to dynamically iterate over an enum that has non-incre

15条回答
  •  走了就别回头了
    2021-01-31 16:09

    Since an enum does not allow iteration you have to create an alternative representation of the enum and its range of values.

    The approach that I would take would be a simple table lookup embedded in a class. The problem is that as the API modifies its enum with new entries, you would also need to update the constructor for this class.

    The simple class that I would use would be comprised of a constructor to build the table along with a couple of methods to iterate over the table. Since you also want to know if there is a problem with table size when adding items, you could use an assert () macro that would issue an assert() in debug mode. In the source example below I use the preprocessor to test for debug compile or not and whether assert has been included or not so as to provide a mechanism for basic consistency checking.

    I have borrowed an idea I saw from P. J. Plauger in his book Standard C Library of using a simple lookup table for ANSI character operations in which the character is used to index into a table.

    To use this class you would do something like the following which uses a for loop to iterate over the set of values in the table. Within the body of the loop you would do whatever you want to do with the enum values.

    CapiEnum myEnum;
    
    for (CAPI_SUBTYPE_E jj = myEnum.Begin(); !myEnum.End(); jj = myEnum.Next()) {
         // do stuff with the jj enum value
    }
    

    Since this class enumerates over the values, I have arbitrarily chosen to return the value of CAPI_SUBTYPE_NULL in those cases where we have reached the end of the enumeration. So the return value in the case of a table lookup error is in the valid range however it can not be depended upon. There fore a check on the End() method should be done to see if the end of an iteration has been reached. Also after doing the construction of the object one can check the m_bTableError data member to see if there was an error during construction.

    The source example for the class follows. You would need to update the constructor with the enum values for the API as they change. Unfortunately there is not much that can be done to automate the check on an update enum however we do have tests in place in a debug compile to check that the table is large enough and that the value of the enum being put into the table is within range of the table size.

    class CapiEnum {
    public:
        CapiEnum (void);                                // constructor
        CAPI_SUBTYPE_E  Begin (void);                   // method to call to begin an iteration
        CAPI_SUBTYPE_E  Next (void);                    // method to get the next in the series of an iteration
        bool            End (void);                     // method to indicate if we have reached the end or not
        bool            Check (CAPI_SUBTYPE_E value);   // method to see if value specified is in the table
        bool  m_TableError;
    private:
        static const int m_TableSize = 256;    // set the lookup table size
        static const int m_UnusedTableEntry = -1;
        int   m_iIterate;
        bool  m_bEndReached;
        CAPI_SUBTYPE_E  m_CapiTable[m_TableSize];
    };
    
    #if defined(_DEBUG)
    #if defined(assert)
    #define ADD_CAPI_ENUM_ENTRY(capi) (((capi) < m_TableSize && (capi) > m_UnusedTableEntry) ? (m_CapiTable[(capi)] = (capi)) : assert(((capi) < m_TableSize) && ((capi) > m_UnusedTableEntry)))
    #else
    #define ADD_CAPI_ENUM_ENTRY(capi) (((capi) < m_TableSize && (capi) > m_UnusedTableEntry) ? (m_CapiTable[(capi)] = (capi)) : (m_TableError = true))
    #endif
    #else
    #define ADD_CAPI_ENUM_ENTRY(capi) (m_CapiTable[(capi)] = (capi))
    #endif
    
    CapiEnum::CapiEnum (void) : m_bEndReached(true), m_iIterate(0), m_TableError(false)
    {
        for (int iLoop = 0; iLoop < m_TableSize; iLoop++) m_CapiTable[iLoop] = static_cast  (m_UnusedTableEntry);
        ADD_CAPI_ENUM_ENTRY(CAPI_SUBTYPE_NULL);
        // .....
        ADD_CAPI_ENUM_ENTRY(CAPI_SUBTYPE_DIAG_ASG);
    }
    
    CAPI_SUBTYPE_E CapiEnum::Begin (void)
    {
        m_bEndReached = false;
        for (m_iIterate = 0; m_iIterate < m_TableSize; m_iIterate++) {
            if (m_CapiTable[m_iIterate] > m_UnusedTableEntry) return m_CapiTable[m_iIterate];
        }
        m_bEndReached = true;
        return CAPI_SUBTYPE_NULL;
    }
    
    CAPI_SUBTYPE_E CapiEnum::Next (void)
    {
        if (!m_bEndReached) {
            for (m_iIterate++; m_iIterate < m_TableSize; m_iIterate++) {
                if (m_CapiTable[m_iIterate] > m_UnusedTableEntry) return m_CapiTable[m_iIterate];
            }
        }
        m_bEndReached = true;
        return CAPI_SUBTYPE_NULL;
    }
    
    bool CapiEnum::End (void)
    {
        return m_bEndReached;
    }
    
    bool CapiEnum::Check (CAPI_SUBTYPE_E value)
    {
        return (value >= 0 && value < m_TableSize && m_CapiTable[value] > m_UnusedTableEntry);
    }
    

    And if you like you could add an additional method to retrieve the current value of the iteration. Notice that rather than incrementing to the next, the Current() method uses whatever the iteration index is currently at and starts searching from the current position. So if the current position is a valid value it just returns it otherwise it will find the first valid value. Alternatively, you could make this just return the current table value pointed to by the index and if the value is invalid, set an error indicator.

    CAPI_SUBTYPE_E CapiEnum::Current (void)
    {
        if (!m_bEndReached) {
            for (m_iIterate; m_iIterate < m_TableSize; m_iIterate++) {
                if (m_CapiTable[m_iIterate] > m_UnusedTableEntry) return m_CapiTable[m_iIterate];
            }
        }
        m_bEndReached = true;
        return CAPI_SUBTYPE_NULL;
    }
    

提交回复
热议问题