2013-10-09 4 views
11

Я ищу, чтобы использовать набор битовых флагов для моей текущей проблемы. Эти флаги (красиво) определены как часть enum, однако я понимаю, что когда вы используете OR два значения из перечисления, тип возврата операции OR имеет тип int.Введите безопасные флаги бит enum

То, что я в настоящее время ищет решение, которое позволит пользователям битовой маски, чтобы оставаться в безопасности типа, как таковой я создал такую ​​перегрузку operator |

enum ENUM 
{ 
    ONE  = 0x01, 
    TWO  = 0x02, 
    THREE = 0x04, 
    FOUR = 0x08, 
    FIVE = 0x10, 
    SIX  = 0x20 
}; 

ENUM operator | (ENUM lhs, ENUM rhs) 
{ 
    // Cast to int first otherwise we'll just end up recursing 
    return static_cast<ENUM>(static_cast<int>(lhs) | static_cast<int>(rhs)); 
} 

void enumTest(ENUM v) 
{ 
} 

int main(int argc, char **argv) 
{ 
    // Valid calls to enumTest 
    enumTest(ONE | TWO | FIVE); 
    enumTest(TWO | THREE | FOUR | FIVE); 
    enumTest(ONE | TWO | THREE | FOUR | FIVE | SIX); 

    return 0; 
} 

ли действительно обеспечить это перегрузка типа безопасности? Ли листинг int, содержащий значения, не определенные в перечислении, вызывает неопределенное поведение? Есть ли какие-либо оговорки, о которых нужно знать?

+0

'оператора | (пяти-, шести-) == 0x30'. Что константа ENUM имеет значение '0x30'? – Adam

+0

Я бы не делал никакой формы сравнения по результирующему значению, я бы просто проверял флаги. – ctor

+1

Тогда результат ИЛИ должен оставаться в int. – Adam

ответ

5

ли это перегрузка действительно обеспечить безопасность типов?

В этом случае да.Действительный диапазон значений для перечисления, по крайней мере, до (но не обязательно включает) следующую наибольшую мощность двух после самого большого именованного перечислителя, чтобы позволить ему использоваться для битмаксов, подобных этому. Таким образом, любая побитовая операция над двумя значениями даст значение, представляемое этим типом.

Выполняет ли листинг значение int, которое не определено в перечислении, вызывает неопределенное поведение?

Нет, если значения представляются перечислением, которое они здесь.

Есть ли какие-либо оговорки, о которых нужно знать?

Если вы выполняли такие операции, как арифметика, которая могла принимать значение за пределами допустимого диапазона, то вы получите результат, определяемый реализацией, но не неопределенный поведенческий.

+0

Под «пока значения представляются перечислением», вы имеете в виду, что значения должны быть представлены под базовым типом перечисления? – Carlton

3

Значения ваших констант не закрываются под ИЛИ. Другими словами, вполне возможно, что результатом OR двух постоянных ENUM приведет к значению, которое не является постоянной ENUM:

0x30 == FIVE | SIX; 

Стандарт говорит, что это нормально, enumaration может иметь значение не равный любому из его enumarators (констант). Предположительно, это позволит использовать этот тип использования.

На мой взгляд это не типобезопасен, потому что если вы посмотрите на реализацию enumTest вы должны знать, что тип аргумента ENUM, но это может иметь значение, не являющееся ENUM переписчик.

Я думаю, что если это просто битовые флаги, то сделайте то, что вам хочет компилятор: используйте int для комбинации флагов.

+1

Листинг хорошо определен, если результат является допустимым значением перечисления - как он есть здесь. Допустимыми значениями являются не только именованные перечисления, но (по крайней мере) все до следующей наибольшей мощности двух - это указано так, чтобы разрешить именно такое использование. –

+0

@MikeSeymour У вас есть ссылка на это? Все, что я нахожу, говорит, что это незаконно. – Adam

+0

C++ 11 7.2. Пункт 7 является наиболее актуальным, поскольку он определяет диапазон значений. –

2

С простым enum, такие, как ваш:

enum ENUM 
{ 
    ONE  = 0x01, 
    TWO  = 0x02, 
    ... 
}; 

это определяется реализацией, что это базовый тип (скорее всего int) , но до тех пор, как вы собираетесь использовать | (побитовое или) для создания масок, результат никогда не потребует более широкого типа, чем наибольшее значение из этого перечисления.


[1]«Основной тип перечисления является интегральным типом, который может представлять все значения перечислителя, определенные в перечислении. Это определяется реализация которого интегральный типа используется как базовый тип для перечисления, за исключением того, что базовый тип не должен превышать int, если значение перечислителя не может быть помещено в int или unsigned int. "

4

Если вы думаете о безопасности типа, то лучше использовать std::bitset

enum BITS { A, B, C, D }; 
std::bitset<4> bset, bset1; 
bset.set(A); bset.set(C); 
bset1[B] = 1; 
assert(bset[A] == bset[C]); 
assert(bset[A] != bset[B]); 
assert(bset1 != bset); 
1

Это мой подход к битовых флагов:

template<typename E> 
class Options { 
     unsigned long values; 
     constexpr Options(unsigned long v, int) : values{v} {} 
    public: 
     constexpr Options() : values(0) {} 
     constexpr Options(unsigned n) : values{1UL << n} {} 
     constexpr bool operator==(Options const& other) const { 
     return (values & other.values) == other.values; 
     } 
     constexpr bool operator!=(Options const& other) const { 
     return !operator==(other); 
     } 
     constexpr Options operator+(Options const& other) const { 
     return {values | other.values, 0}; 
     } 
     Options& operator+=(Options const& other) { 
     values |= other.values; 
     return *this; 
     } 
     Options& operator-=(Options const& other) { 
     values &= ~other.values; 
     return *this; 
     } 
}; 

#define DECLARE_OPTIONS(name) class name##__Tag; using name = Options 
#define DEFINE_OPTION(name, option, index) constexpr name option(index) 

Вы можете использовать его так:

DECLARE_OPTIONS(ENUM); 
DEFINE_OPTIONS(ENUM, ONE, 0); 
DEFINE_OPTIONS(ENUM, TWO, 1); 
DEFINE_OPTIONS(ENUM, THREE, 2); 
DEFINE_OPTIONS(ENUM, FOUR, 3); 

Тогда ONE + TWO еще типа ENUM. И вы можете повторно использовать класс для определения нескольких наборов флагов бит, которые имеют разные, несовместимые типы.

Мне лично не нравится использовать | и & для установки и тестирования бит. Это логическая операция, которая должна быть выполнена для установки и тестирования, но они не выражают смысл операции, если вы не думаете о побитовых операциях. Если вы читаете ONE | TWO, вы можете подумать, что хотите либо ОДНОГО, либо ДВА, не обязательно того и другого. Вот почему я предпочитаю использовать + для добавления флагов вместе и ==, чтобы проверить, установлен ли флаг.

Смотрите здесь для более подробной информации о моей предложил реализации: http://www.crisluengo.net/index.php/archives/851

Смежные вопросы