простори імен для типів перерахунків - найкращі практики


102

Часто потрібно кілька перелічених типів разом. Іноді в одному є зіткнення імені. Для цього приходять два рішення: використовувати простір імен або використовувати «більші» імена елементів перерахунку. Проте рішення простору імен має дві можливі реалізації: фіктивний клас із вкладеною перерахунком або повний роздутий простір імен.

Я шукаю плюси і мінуси всіх трьох підходів.

Приклад:

// 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: //...
        //...
    }
  }

19
Перш за все, я б використав Колір :: Червоний, Відчуття: Сердитий тощо.
Абатіщев

гарне запитання, я використав метод простору імен ....;)
MiniScalope

18
префікс "c" на все шкодить читабельності.
Користувач

8
@User: чому, дякую за відкриття угорської дискусії :)
xtofl

5
Зауважте, що вам не потрібно називати enum як in enum e {...}, enums можуть бути анонімними, тобто enum {...}, що має набагато більше сенсу, коли він перебуває в просторі імен чи класах.
кралик

Відповіді:


73

Оригінальна відповідь C ++ 03:

Вигода від А namespace(більше class), що ви можете використовувати usingоголошення , коли ви хочете.

Проблема з використанням в namespaceтому , що простір імен може бути розширений в інших місцях в коді. У великому проекті вам не було б гарантовано, що два чіткі переліки не вважають, що їх називаютьeFeelings

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

Якщо ви виконуєте будь-яку з цих практик, ви випереджаєте криву і, ймовірно, не потрібно це більше вивчати.

Новіші поради C ++ 11:

Якщо ви використовуєте C ++ 11 або пізнішої версії, enum classбуде неявно обмежувати значення enum в межах імені enum.

З enum classвами ви втратите неявні перетворення та порівняння цілих типів, але на практиці це може допомогти вам розкрити неоднозначний чи помилковий код.


4
Я згоден із структурою-ідеєю. І дякую за комплімент :)
xtofl

3
+1 Я не міг запам'ятати синтаксис C ++ 11 "enum class". Без цієї особливості перерахунки неповні.
Граул

Чи є їх можливість використовувати "використання" для неявної сфери "класу enum". напр., додамо "використання кольору :: e;" для коду дозволяють використовувати 'cRed' і знайте, що це має бути Color :: e :: cRed?
JVApen


11

Я гібридизував попередні відповіді на щось подібне: (EDIT: Це корисно лише для попереднього C ++ 11. Якщо ви використовуєте C ++ 11, використовуйте enum class)

У мене є один великий файл заголовка, який містить усі мої переліки в проекті, оскільки ці перерахунки поділяються між класами робітників, і не має сенсу ставити переліки в самі робітничі класи.

structУникає громадськості: синтаксичний цукру, і 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

Я б точно не уникав використання для цього класу; використовувати натомість простір імен. Питання зводиться до того, чи слід використовувати простір імен або використовувати унікальні ідентифікатори для значень enum. Особисто я б використав простір імен, щоб мої ідентифікатори могли бути коротшими і, сподіваюся, більш зрозумілими. Тоді код програми може використовувати директиву "за допомогою простору імен" і зробити все більш читабельним.

З наведеного вище прикладу:

using namespace Colors;

void setPenColor( const e c ) {
    switch (c) {
        default: assert(false);
        break; case cRed: //...
        break; case cBlue: //...
        //...
    }
}

Чи можете ви підказати, чому ви віддасте перевагу простору імен над класом?
xtofl

@xtofl: ти не можеш писати "за допомогою класу Colors"
MSalters

2
@MSalters: Ви також не можете писати Colors someColor = Red;, оскільки простір імен не є типом. Вам доведеться писати Colors::e someColor = Red;замість цього, що є досить протиінтуїтивним.
SasQ

@SasQ Чи не доведеться вам використовувати Colors::e someColorнавіть з a, struct/classякщо ви хочете використовувати його у switchвиписці? Якщо ви використовуєте анонімний, enumкомутатор не зможе оцінити struct.
Енігма Макбета

1
Вибачте, але const e cмені здається важко читабельним :-) Не робіть цього. Однак використання простору імен - це добре.
dhaumann

7

Різниця між використанням класу або простору імен полягає в тому, що клас не може бути повторно відкритий, як може. Це дозволяє уникнути можливості зловживання простором імен у майбутньому, але також існує проблема, яку ви також не можете додати до набору перерахунків.

Можливою перевагою для використання класу є те, що вони можуть використовуватися як аргументи типу шаблону, що не стосується просторів імен:

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

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

Особисто я не прихильник використання , і я віддаю перевагу цілком кваліфікованим іменам, тому я не сприймаю це як плюс для просторів імен. Однак це, мабуть, не найважливіше рішення, яке ви приймете у своєму проекті!


Невідкриття занять також є потенційним недоліком. Список кольорів також не є кінцевим.
MSalters

1
Я думаю, що не переймати клас - це потенційна перевага. Якщо я хочу більше кольорів, я б просто перекомпілював клас з більшою кількістю кольорів. Якщо я не можу цього зробити (скажімо, у мене немає коду), тоді я не хочу доторкатися до нього.
Томас Едінг

@MSalters: Немає можливості знову відкрити клас - це не тільки недолік, але й інструмент безпеки. Тому що, коли можна було б знову відкрити клас та додати деякі значення до enum, він може порушити інший код бібліотеки, який вже залежить від цього enum і знає лише старий набір значень. Тоді він із задоволенням прийме ці нові цінності, але перерветься під час виконання, не знаючи, що з ними робити. Пам'ятайте принцип відкритого закриття: Клас повинен бути закритим для зміни , але відкритим для розширення . Під розширенням я маю на увазі не додавання до існуючого коду, а перенесення його новим кодом (напр., Отримання).
SasQ

Тож, коли ви хочете розширити перерахунок, вам слід зробити його новим типом, похідним від першого (якби це було легко можливо в C ++ ...; /). Тоді він міг би бути безпечно використаний новим кодом, який розуміє ці нові значення, але старі коди будуть прийняті лише старі значення (шляхом їх перетворення вниз). Вони не повинні сприймати жодне з цих нових значень як неправильне (розширене). Тільки старі значення, які вони розуміють, приймаються як правильні (базові) типи (і випадково також є новим типом, тому вони можуть бути прийняті і новим кодом).
SasQ

7

Перевага використання класу полягає в тому, що над ним можна побудувати повноцінний клас.

#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заяві.


Є чи це рішення яким - то чином пов'язані з тим, що показано тут?: En.wikibooks.org/wiki/More_C%2B%2B_Idioms/Type_Safe_Enum Я думаю про те, як зробити це шаблон , який я не повинен переписати цей патерн кожен раз Мені потрібно його використовувати.
SasQ

@SasQ: схоже, схоже, так. Це, мабуть, та сама ідея. Однак я не впевнений, чи корисний шаблон, якщо ви не додасте туди багато "загальних" методів.
Michał Górny

1. Не дуже правда. Ви можете перевірити час компіляції, щоб перерахунок був дійсним, через const_expr або через приватний конструктор для int.
xryl669

5

Оскільки переліки охоплюють їх обширну область, можливо, найкраще обернути їх чимось, щоб уникнути забруднення глобального простору імен та допомогти уникнути зіткнень імен. Я віддаю перевагу простору імен перед класом просто тому, що namespaceвідчуває себе мішком із триманням, тоді як classвідчуває себе надійним предметом (пор. structПроти classдебатів). Можлива перевага для простору імен полягає в тому, що він може бути розширений пізніше - корисно, якщо ви маєте справу з стороннім кодом, який ви не можете змінити.

Це все суперечливо, коли ми отримуємо курси перерахування з C ++ 0x.


перелічіть класи ... треба це шукати!
xtofl

3

Я також схильний обертати свої перерахунки на заняттях.

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

У мене є спеціальний панель інструментів :: клас 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

Ще раз головний поверне помилку.

Проблема полягає в тому, що компілятор помістить enum у найменшому доступному представленні (тут нам потрібні 2 біти) і що все, що відповідає цьому представництву, вважається дійсним значенням.

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

Взагалі мій маленький помічник дійсно полегшує речі для моїх перерахунків (звичайно, це додає трохи накладних витрат), і це можливо лише тому, що я гнізджую кожного перерахунку у своїй структурі :)


4
Цікаво. Ви не проти поділитися визначенням вашого класу Enum?
momeara
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.