С++ Назначение явных значений Enums с использованием сдвига битов

Я просматривал некоторый код из проектов с открытым исходным кодом и заметил, что более чем в одном случае значения перечислений присваивались путем битового сдвига значения на возрастающее количество мест. Я не вижу какой-либо конкретной причины для этого и не вижу повышения эффективности по сравнению с присвоением значений путем простого увеличения +1.

Несмотря на это, это, вероятно, не имеет большого смысла без некоторого кода, чтобы продемонстрировать, что меня смутило.

Класс 1

enum EventType {
        NONE                = 0,
        PUSH                = 1<<0,
        RELEASE             = 1<<1,
        DOUBLECLICK         = 1<<2,
        DRAG                = 1<<3,
        MOVE                = 1<<4,
        KEYDOWN             = 1<<5,
        KEYUP               = 1<<6,
        FRAME               = 1<<7,
        RESIZE              = 1<<8,
        SCROLL              = 1<<9,
        PEN_PRESSURE        = 1<<10,
        PEN_ORIENTATION     = 1<<11,
        PEN_PROXIMITY_ENTER = 1<<12,
        PEN_PROXIMITY_LEAVE = 1<<13,
        CLOSE_WINDOW        = 1<<14,
        QUIT_APPLICATION    = 1<<15,
        USER                = 1<<16
    };

Класс 2

    enum EventType {
        EVENT_MOUSE_DOUBLE_CLICK = osgGA::GUIEventAdapter::DOUBLECLICK,
        EVENT_MOUSE_DRAG         = osgGA::GUIEventAdapter::DRAG,
        EVENT_KEY_DOWN           = osgGA::GUIEventAdapter::KEYDOWN,
        EVENT_SCROLL             = osgGA::GUIEventAdapter::SCROLL,
        EVENT_MOUSE_CLICK        = osgGA::GUIEventAdapter::USER << 1,
        EVENT_MULTI_DRAG         = osgGA::GUIEventAdapter::USER << 2,   // drag with 2 fingers
        EVENT_MULTI_PINCH        = osgGA::GUIEventAdapter::USER << 3,   // pinch with 2 fingers
        EVENT_MULTI_TWIST        = osgGA::GUIEventAdapter::USER << 4    // drag 2 fingers in different directions
    };

Если я правильно понимаю, EventType::USER имеет явное значение 65536 или 100000000000000000 в двоичном формате. EVENT_MULTI_TWIST имеет значение 1048576 или 100000000000000000000 в двоичном формате.

Какова была бы цель присвоения значений перечисления таким образом, а не просто иметь что-то вроде этого:

enum EventType {
        NONE                = 0,
        PUSH                = 1,
        RELEASE             = 2,
        DOUBLECLICK         = 3,
        DRAG                = 4,
        MOVE                = 5,
        KEYDOWN             = 6,
        KEYUP               = 7,
        FRAME               = 8,
        RESIZE              = 9,
        SCROLL              = 10,
        PEN_PRESSURE        = 11,
        PEN_ORIENTATION     = 12,
        PEN_PROXIMITY_ENTER = 13,
        PEN_PROXIMITY_LEAVE = 14,
        CLOSE_WINDOW        = 15,
        QUIT_APPLICATION    = 16,
        USER                = 17
    };

person mdoran3844    schedule 17.09.2013    source источник
comment
Это битовое поле. Вы можете объединить несколько перечислителей с помощью побитового или (|) и сохранить результат в одном объекте типа EventType.   -  person dyp    schedule 17.09.2013


Ответы (3)


Обычная причина для этого состоит в том, чтобы связать каждое значение перечисления с одним битом конечного значения v, поэтому вы можете (например) закодировать несколько флагов в одну переменную.

Например, в типичной 32-битной системе вы можете закодировать (что вполне очевидно) 32 отдельных флага в один 32-битный int (или, что предпочтительнее, unsigned int).

Например, если вы смотрите на клавиши на клавиатуре, вы можете закодировать «обычные» клавиши, такие как буквы и цифры, в один байт (вероятно, используя последовательные значения, как вы предложили), и клавиши-модификаторы, такие как shift, alt и control как отдельные биты. Это позволит вам (например) закодировать что-то вроде control+alt+A как одно значение.

Точно так же для сообщений мыши вы можете рассматривать кнопки мыши как «модификаторы», поэтому вы можете кодировать что-то вроде перетаскивания мыши как одно значение.

В обоих случаях важным моментом кодирования «модификаторов» в виде отдельных битов является то, что это позволяет вам позже получить эти модификаторы однозначно — если в значении установлен правильный бит, то этот модификатор был использован. Напротив, если вы просто используете последовательные числа, вы не сможете впоследствии извлечь отдельные фрагменты. Например, если у вас есть входные данные, закодированные как 1, 2 и 3, вы не можете сказать, предназначен ли 3 для обозначения исходного ввода, соответствующего 3, или входных данных как 1, так и 2 одновременно. Однако если вы кодируете значения как 1, 2 и 4, вы можете комбинировать значения и по-прежнему декодировать их, чтобы точно видеть, какие входные данные были необходимы для получения определенного значения.

person Jerry Coffin    schedule 17.09.2013
comment
Отличный ответ, имеет большой смысл. Я думаю, я никогда не рассматривал несколько событий, происходящих симуляционно, но имеет смысл поддерживать такую ​​​​вещь. - person mdoran3844; 17.09.2013

Это один из тех случаев, когда, поскольку вещь может быть реализована на C++, не добавляется никакого механизма для ее явной реализации.

перечисления предоставляют видимые компоновщику/отладчику константы и могут быть объединены в классы и шаблоны, поэтому, хотя это не совсем то, чего пытается достичь конечный пользователь, и, конечно же, не делает этого явно, тот факт, что значения в выражение enum не обязательно должно быть последовательным означает, что оно считается достаточным для реализации перечислимых битовых масок.

Полученные значения можно использовать непосредственно в битовых масках, например:

    enum {
        Widget = 1 << 0,  // value b00000001
        Dingo = 1 << 1,   // value b00000010
        Herp = 1 << 2     // value b00000100
    };

    if (flag & Widget)
        doWidgetThings();
    if (flag & Dingo)
        feedTheDog(baby);
    if (flag & Herp)
        win();

Это способно одновременно принимать 0, 1 или более значений в «флаге»:

    flag = 0; // matches none
    flag = Herp; // matches one flag.
    flag = Widget | Dingo; // sets flag to have both widget and dingo.
person kfsone    schedule 17.09.2013

Причина, по которой вы хотели бы это сделать, заключается в том, что вы можете использовать перечисления, определенные таким образом, как битовые маски. Учитывая 32-битное целое число, у вас может быть 32 уникальных характеристики, которые можно включить или отключить, используя побитовые комбинации перечисленных значений.

Например, если вы хотите создать обработчик событий, который прослушивает QUIT_APPLICATION и CLOSE_WINDOW в реализации, использующей битовые сдвиги, вы можете просто использовать QUIT_APPLICATION | CLOSE_WINDOW. Каждое перечисление представляет собой отдельный бит, и объединение их вместе позволяет вам легко указывать все типы событий, которые вы хотите, так, как вы не смогли бы сделать, если бы они были просто пронумерованы от 1 до 16.

Подумайте о случае, когда вам нужен обработчик событий, который прослушивает PUSH и RELEASE, используя предложенное вами «более эффективное» перечисление. Если бы вы ИЛИ их вместе, вы получили бы 3. 3 также является значением DOUBLECLICK в вашем примере. Таким образом, нет никакого способа узнать, какие события вы действительно хотели; это была комбинация PUSH и RELEASE или просто DOUBLECLICK? Вот почему резервирование одного бита для каждого из перечисленных типов так эффективно.

Вы часто будете видеть перечисляемые значения, определенные следующим образом:

enum EventType {
    NONE                =       0,
    PUSH                =     0x1,
    RELEASE             =     0x2,
    DOUBLECLICK         =     0x4,
    DRAG                =     0x8,
    MOVE                =    0x10,
    KEYDOWN             =    0x20,
    KEYUP               =    0x40,
    FRAME               =    0x80,
    RESIZE              =   0x100,
    SCROLL              =   0x200,
    PEN_PRESSURE        =   0x400,
    PEN_ORIENTATION     =   0x800,
    PEN_PROXIMITY_ENTER =  0x1000,
    PEN_PROXIMITY_LEAVE =  0x2000,
    CLOSE_WINDOW        =  0x4000,
    QUIT_APPLICATION    =  0x8000,
    USER                = 0x10000
};

Иногда вы даже увидите такие вещи, как:

    ...
    CLOSE_WINDOW        =  0x4000,
    QUIT_APPLICATION    =  0x8000,
    CLOSE_AND_QUIT      =  CLOSE_WINDOW | QUIT_APPLICATION,
    ...

Обозначение битового сдвига просто легче читать (для некоторых людей), они оба делают одно и то же. Каждое перечисляемое значение представляет один бит или дискретную комбинацию битов (за исключением NONE, которое представляет отсутствие всех битов).

person Andon M. Coleman    schedule 17.09.2013