пространства имен для типов enum - лучшие практики - PullRequest
95 голосов
/ 27 января 2009

Часто требуется несколько перечисляемых типов вместе. Иногда у каждого есть столкновение имен. На ум приходят два решения: использовать пространство имен или использовать «большие» имена элементов перечисления. Тем не менее, решение пространства имен имеет две возможные реализации: фиктивный класс с вложенным перечислением или полное пространство имен.

Я ищу плюсы и минусы всех трех подходов.

Пример:

// oft seen hand-crafted name clash solution
enum eColors { cRed, cColorBlue, cGreen, cYellow, cColorsEnd };
enum eFeelings { cAngry, cFeelingBlue, cHappy, cFeelingsEnd };
void setPenColor( const eColors c ) {
    switch (c) {
        default: assert(false);
        break; case cRed: //...
        break; case cColorBlue: //...
        //...
    }
 }


// (ab)using a class as a namespace
class Colors { enum e { cRed, cBlue, cGreen, cYellow, cEnd }; };
class Feelings { enum e { cAngry, cBlue, cHappy, cEnd }; };
void setPenColor( const Colors::e c ) {
    switch (c) {
        default: assert(false);
        break; case Colors::cRed: //...
        break; case Colors::cBlue: //...
        //...
    }
 }


 // a real namespace?
 namespace Colors { enum e { cRed, cBlue, cGreen, cYellow, cEnd }; };
 namespace Feelings { enum e { cAngry, cBlue, cHappy, cEnd }; };
 void setPenColor( const Colors::e c ) {
    switch (c) {
        default: assert(false);
        break; case Colors::cRed: //...
        break; case Colors::cBlue: //...
        //...
    }
  }

Ответы [ 8 ]

65 голосов
/ 27 января 2009

Оригинальный ответ C ++ 03:

Преимущество от namespace (более class) заключается в том, что вы можете использовать объявления using, когда захотите.

Проблема с использованием namespace заключается в том, что пространства имен могут быть расширены в другом месте кода. В большом проекте вам не гарантируется, что два различных перечисления не думают, что они называются eFeelings

Для более простого кода я использую struct, так как вы, вероятно, хотите, чтобы содержимое было общедоступным.

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

Новее, C ++ 11 совет:

Если вы используете C ++ 11 или более позднюю версию, enum class будет неявно включать значения перечисления в имя перечисления.

С enum class вы потеряете неявные преобразования и сравнения с целочисленными типами, но на практике это может помочь вам обнаружить неоднозначный или ошибочный код.

20 голосов
/ 28 января 2009

FYI В C ++ 0x есть новый синтаксис для случаев, подобных тому, что вы упомянули (см. C ++ 0x wiki page )

enum class eColors { ... };
enum class eFeelings { ... };
11 голосов
/ 20 февраля 2013

Я гибридизировал предыдущие ответы с чем-то вроде этого: (РЕДАКТИРОВАТЬ: Это полезно только для до C ++ 11. Если вы используете C ++ 11, используйте enum class)

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

struct избегает использования public: синтаксического сахара, а typedef позволяет вам фактически объявлять переменные этих перечислений в других рабочих классах.

Я не думаю, что использование пространства имен помогает вообще. Может быть, это потому, что я программист на C #, и у вас есть , чтобы использовать имя типа enum при ссылке на значения, так что я к этому привык.

    struct KeySource {
        typedef enum { 
            None, 
            Efuse, 
            Bbram
        } Type;
    };

    struct Checksum {
        typedef enum {
            None =0,
            MD5 = 1,
            SHA1 = 2,
            SHA2 = 3
        } Type;
    };

    struct Encryption {
        typedef enum {
            Undetermined,
            None,
            AES
        } Type;
    };

    struct File {
        typedef enum {
            Unknown = 0,
            MCS,
            MEM,
            BIN,
            HEX
        } Type;
    };

...

class Worker {
    File::Type fileType;
    void DoIt() {
       switch(fileType) {
       case File::MCS: ... ;
       case File::MEM: ... ;
       case File::HEX: ... ;
    }
}
9 голосов
/ 27 января 2009

Я бы определенно не использовал для этого класс; используйте вместо этого пространство имен. Вопрос сводится к тому, использовать ли пространство имен или использовать уникальные идентификаторы для значений перечисления. Лично я бы использовал пространство имен, чтобы мои идентификаторы были короче и, я надеюсь, более понятными. Тогда код приложения может использовать директиву using namespace и сделать все более читабельным.

Из приведенного выше примера:

using namespace Colors;

void setPenColor( const e c ) {
    switch (c) {
        default: assert(false);
        break; case cRed: //...
        break; case cBlue: //...
        //...
    }
}
7 голосов
/ 22 апреля 2012

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

#include <cassert>

class Color
{
public:
    typedef enum
    {
        Red,
        Blue,
        Green,
        Yellow
    } enum_type;

private:
    enum_type _val;

public:
    Color(enum_type val = Blue)
        : _val(val)
    {
        assert(val <= Yellow);
    }

    operator enum_type() const
    {
        return _val;
    }
};

void SetPenColor(const Color c)
{
    switch (c)
    {
        case Color::Red:
            // ...
            break;
    }
}

Как показывает приведенный выше пример, используя класс, вы можете:

  1. запрещает (к сожалению, не во время компиляции) C ++ разрешать приведение из недопустимого значения,
  2. установить (ненулевое) значение по умолчанию для вновь создаваемых перечислений,
  3. добавить дополнительные методы, например, для возврата строкового представления выбора.

Просто отметьте, что вам нужно объявить operator enum_type(), чтобы C ++ знал, как преобразовать ваш класс в базовое перечисление. В противном случае вы не сможете передать тип в оператор switch.

7 голосов
/ 27 января 2009

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

Возможное преимущество использования классов в том, что они могут использоваться в качестве аргументов типа шаблона, что не относится к пространствам имен:

class Colors {
public:
  enum TYPE {
    Red,
    Green,
    Blue
  };
};

template <typename T> void foo (T t) {
  typedef typename T::TYPE EnumType;
  // ...
}

Лично я не фанат , использующего , и я предпочитаю полностью определенные имена, поэтому я не считаю это плюсом для пространств имен. Однако это, вероятно, не самое важное решение, которое вы примете в своем проекте!

5 голосов
/ 27 января 2009

Поскольку перечисления ограничены областью охвата, вероятно, лучше обернуть их в что-то , чтобы избежать загрязнения глобального пространства имен и избежать конфликтов имен. Я предпочитаю пространство имен классу просто потому, что namespace ощущается как мешок с удержанием, тогда как class ощущается как надежный объект (см. Дискуссию struct против class). Возможное преимущество пространства имен состоит в том, что оно может быть расширено позже - полезно, если вы имеете дело со сторонним кодом, который вы не можете изменить.

Это все спорно, конечно, когда мы получаем классы перечислений с C ++ 0x.

3 голосов
/ 24 сентября 2009

Я также склоняюсь к тому, чтобы обернуть свои списки в классах.

Как сообщил Ричард Корден, преимущество класса состоит в том, что он является типом в смысле c ++, и поэтому вы можете использовать его с шаблонами.

У меня есть специальный класс toolbox :: Enum для моих нужд, который я специализирую для каждого шаблона, который предоставляет базовые функции (в основном: отображение значения enum в std :: string, чтобы облегчить чтение операций ввода-вывода).

Мой маленький шаблон также имеет дополнительное преимущество - он действительно проверяет допустимые значения. Компилятор довольно слабо проверяет, действительно ли значение находится в перечислении:

typedef enum { False: 0, True: 2 } boolean;
   // The classic enum you don't want to see around your code ;)

int main(int argc, char* argv[])
{
  boolean x = static_cast<boolean>(1);
  return (x == False || x == True) ? 0 : 1;
} // main

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

Аналогично:

typedef enum { Zero: 0, One: 1, Two: 2 } example;

int main(int argc, char* argv[])
{
  example y = static_cast<example>(3);
  return (y == Zero || y == One || y == Two) ? 0 : 1;
} // main

Еще раз main вернет ошибку.

Проблема в том, что компилятор поместит перечисление в наименьшее доступное представление (здесь нам нужно 2 бита) и что все, что помещается в это представление, считается допустимым значением.

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

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

...