Чи варто використовувати #define, enum чи const?


125

У проекті C ++, над яким я працюю, у мене є значення типу прапора, яке може мати чотири значення. Ці чотири прапори можна комбінувати. Прапори описують записи в базі даних і можуть бути:

  • новий запис
  • видалений запис
  • модифікований запис
  • існуючий запис

Тепер для кожного запису я хочу зберегти цей атрибут, щоб я міг використовувати enum:

enum { xNew, xDeleted, xModified, xExisting }

Однак в інших місцях коду мені потрібно вибрати записи, які повинні бути видимі користувачеві, тому я хотів би мати можливість передавати це як єдиний параметр, наприклад:

showRecords(xNew | xDeleted);

Тож, мабуть, у мене є три можливі підходи:

#define X_NEW      0x01
#define X_DELETED  0x02
#define X_MODIFIED 0x04
#define X_EXISTING 0x08

або

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

або

namespace RecordType {
    static const uint8 xNew = 1;
    static const uint8 xDeleted = 2;
    static const uint8 xModified = 4;
    static const uint8 xExisting = 8;
}

Вимоги до простору важливі (байт проти int), але не є вирішальними. За допомогою заданих параметрів я втрачаю безпеку типу, а також enumвтрачаю деякий простір (цілі числа), і, ймовірно, доведеться відкидати, коли хочу виконати побітну операцію. З constЯ думаю , що я теж втратить тіпобезопасность , так як випадкові uint8могли помилково.

Чи є ще якийсь чистіший спосіб?

Якщо ні, то що б ви використовували і чому?

PS Решта коду - досить чистий сучасний C ++ без #defines, і я використовував простори імен та шаблони в декількох просторах, так що і про них не виникає сумнівів.


"з перерахунком я втрачаю деякий простір (цілі числа)". Не обов'язково. Див stackoverflow.com/questions/366017 / ... і stackoverflow.com/questions/1113855 / ... (і ССЗ -fshort-перерахування (я припускаю , що ці відповіді C все ще вірно в C ++) ..
idbrii

@pydave Якщо ви не впевнені в сумісності C і C ++, я вважаю це посилання дуже корисним, див., наприклад, enum david.tribble.com/text/cdiffs.htm#C99-enum-type
aka.nice

3
Це старіша тема з високими голосами, чи є причина не згадувати класи C ++ 11 перерахунків для цієї проблемної ситуації.
Брандін

Як зауваження, enum RecordType : uint8_tпоєднує безпеку типу enumз малим розміром uint8_t, хоча вам все одно потрібно надати побітові оператори.
Час Джастіна -

Відповіді:


88

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

Помістіть enum у простір імен, щоб запобігти забрудненню констант глобальним простором імен.

namespace RecordType {

Перерахунок оголошує та визначає перевірений час компіляції, який перевіряється. Завжди використовуйте перевірку типу часу компіляції, щоб переконатися, що аргументам та змінним надано правильний тип. Немає потреби в typedef в C ++.

enum TRecordType { xNew = 1, xDeleted = 2, xModified = 4, xExisting = 8,

Створіть іншого члена для недійсного стану. Це може бути корисно як код помилки; наприклад, коли ви хочете повернути стан, але операція вводу / виводу не вдається. Він також корисний для налагодження; використовуйте його у списках ініціалізації та деструкторах, щоб знати, чи слід використовувати значення змінної.

xInvalid = 16 };

Подумайте, що у вас є два цілі для цього типу. Для відстеження поточного стану запису та створення маски для вибору записів у певних станах. Створіть вбудовану функцію, щоб перевірити, чи значення типу є дійсним для вашої мети; як маркер держави проти маски держави. Це дозволить наздогнати помилки, оскільки значення typedefє просто intі таким значенням, яке 0xDEADBEEFможе бути у вашій змінній через неініціалізовані або неправильно встановлені змінні.

inline bool IsValidState( TRecordType v) {
    switch(v) { case xNew: case xDeleted: case xModified: case xExisting: return true; }
    return false;
}

 inline bool IsValidMask( TRecordType v) {
    return v >= xNew  && v < xInvalid ;
}

Додайте usingдирективу, якщо ви хочете використовувати тип часто.

using RecordType ::TRecordType ;

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

Ось кілька прикладів, щоб зібрати все це разом.

void showRecords(TRecordType mask) {
    assert(RecordType::IsValidMask(mask));
    // do stuff;
}

void wombleRecord(TRecord rec, TRecordType state) {
    assert(RecordType::IsValidState(state));
    if (RecordType ::xNew) {
    // ...
} in runtime

TRecordType updateRecord(TRecord rec, TRecordType newstate) {
    assert(RecordType::IsValidState(newstate));
    //...
    if (! access_was_successful) return RecordType ::xInvalid;
    return newstate;
}

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


1
Переважно приємна відповідь - але питання передбачає, що прапори можна комбінувати, а функція IsValidState () не дозволяє поєднувати їх.
Джонатан Леффлер

3
@ Джонатан Леффлер: звідки я стою, я думаю, що "IsValidState" не повинен цього робити, "IsValidMask".
Жоао Портела

1
Бажано, щоб IsValidMaskце не дозволяло вибрати жодне (тобто 0)?
Йоахім Зауер

2
-1 Ідея перевірки типу виконання - гидота.
Ура та хт. - Альф

54

Забудьте про визначення

Вони забруднить ваш код.

бітфілди?

struct RecordFlag {
    unsigned isnew:1, isdeleted:1, ismodified:1, isexisting:1;
};

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

Однак бітові члени в структурах мають практичні недоліки. По-перше, впорядкованість бітів у пам'яті змінюється від компілятора до компілятора. Крім того, багато популярних компіляторів генерують неефективний код для читання та запису членів бітів , і є потенційно серйозні проблеми безпеки потоку пов’язані з бітовими полями (особливо у багатопроцесорних системах) через те, що більшість машин не може маніпулювати довільними наборами бітів у пам'яті, але повинен замість цього завантажувати та зберігати цілі слова. наприклад, наступне не буде безпечним для ниток, незважаючи на використання мутексу

Джерело: http://en.wikipedia.org/wiki/Bit_field :

І якщо вам потрібно більше причин не використовувати бітфілди, можливо, Реймонд Чен переконає вас у своєму "Старому новому реченні" : Аналіз витрат та вигод біт-полів для колекції булів на веб-сайті http://blogs.msdn.com/oldnewthing/ архів / 2008/11/26 / 9143050.aspx

const int?

namespace RecordType {
    static const uint8 xNew = 1;
    static const uint8 xDeleted = 2;
    static const uint8 xModified = 4;
    static const uint8 xExisting = 8;
}

Розмістити їх у просторі імен - це класно. Якщо вони задекларовані у вашій CPP або файлі заголовка, їх значення будуть позначені рядками. Ви зможете використовувати перемикач на ці значення, але це трохи збільшить зв'язок.

А, так: видаліть статичне ключове слово . static є застарілим у C ++ при використанні, як і ви, і якщо uint8 - це тип збірки, вам це не знадобиться, щоб заявити про це у заголовку, що міститься у кількох джерелах одного модуля. Зрештою, код повинен бути:

namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

Проблема такого підходу полягає в тому, що ваш код знає значення ваших констант, що трохи збільшує зв'язок.

перерахувати

Те саме, що і const int, з дещо сильнішим набором тексту.

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

Вони все ще забруднюють глобальний простір імен. До речі ... Видаліть typedef . Ви працюєте в C ++. Ці типи переліків і конструкцій забруднюють код більше, ніж будь-що інше.

Результат такий:

enum RecordType { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } ;

void doSomething(RecordType p_eMyEnum)
{
   if(p_eMyEnum == xNew)
   {
       // etc.
   }
}

Як бачите, ваш перелік забруднює глобальний простір імен. Якщо ви помістите цей перелік в простір імен, у вас вийде щось на кшталт:

namespace RecordType {
   enum Value { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } ;
}

void doSomething(RecordType::Value p_eMyEnum)
{
   if(p_eMyEnum == RecordType::xNew)
   {
       // etc.
   }
}

extern const int?

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

// Header.hpp
namespace RecordType {
    extern const uint8 xNew ;
    extern const uint8 xDeleted ;
    extern const uint8 xModified ;
    extern const uint8 xExisting ;
}

І:

// Source.hpp
namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

Однак ви не зможете використовувати перемикач на ці константи. Тож врешті-решт, виберіть свою отруту ... :-p


5
Чому ви вважаєте, що бітфілди повільні? Чи справді ви профілювали код, використовуючи його та інший метод? Навіть якщо вона є, ясність може бути важливішою, ніж швидкість, зробивши "ніколи не використовувати це" трохи спрощеним.
wnoise

"статичний const uint8 xNew;" є зайвим лише тому, що в C ++ const змінних, що мають область простір імен, за замовчуванням до внутрішніх зв'язків. Видаліть "const" і він має зовнішню зв'язок. Також "enum {...} RecordType;" оголошує глобальну змінну під назвою "RecordType", тип якої - анонімний перелік.
bk1e

onebyone: По-перше, головна причина полягала в тому, що коефіцієнт посилення (кілька байтів, якщо такі є) був переохоплений втратою (повільніше для доступу, і для читання, і для запису) ...
paercebal

3
onebyone: По-друге, весь код, який я створюю на роботі або вдома, є по суті безпечним для потоків. Це легко зробити: Ні глобалі, ні статичні, не поділяються між потоками, якщо не захищені блокуванням. Використання цієї ідіоми порушить цю основну безпеку. А для чого? Кілька байтів можливо ... :-) ...?
paercebal

Додано посилання на статтю Реймонда Чена про приховані витрати на бітфілдах.
paercebal

30

Ви виключали std :: bitset? Набори прапорів - це для чого це. Зробіть

typedef std::bitset<4> RecordType;

тоді

static const RecordType xNew(1);
static const RecordType xDeleted(2);
static const RecordType xModified(4);
static const RecordType xExisting(8);

Оскільки для бітсета є купа перевантажень операторів, тепер ви можете це зробити

RecordType rt = whatever;      // unsigned long or RecordType expression
rt |= xNew;                    // set 
rt &= ~xDeleted;               // clear 
if ((rt & xModified) != 0) ... // test

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

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

Я не купую, що кидання переліків є серйозним недоліком - гаразд, це трохи шумно, а присвоєння значенню поза діапазоном передумови - це невизначене поведінка, тому теоретично можна застрелити себе в ногу на якомусь незвичному C ++ реалізації. Але якщо ви робите це лише при необхідності (це коли йдеться від int до enum iirc), це абсолютно нормальний код, який люди бачили раніше.

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

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

Для переваги я також, до речі, поставив "= 2". Це не обов'язково, але "принцип найменшого здивування" говорить про те, що всі 4 визначення повинні виглядати однаково.


1
Насправді я взагалі не розглядав бітсет. Однак я не впевнений, що це було б добре. За допомогою бітсета я повинен адресувати біти як 1, 2, 3, 4, що зробить код менш читабельним - це означає, що я, мабуть, використовую enum для 'імені' бітів. Можливо, економити місце. Дякую.
Мілан Бабушков

Мілан, вам не доведеться «називати» біти за допомогою enum, ви можете просто використовувати попередньо визначені біти, як показано вище. Якщо ви хочете ввімкнути біт, а не my_bitset.flip (1), ви зробите my_bitset | = xNew;
moswald

це спрямовано менше на вас і більше на STL, але: мені справді потрібно запитати: навіщо ви використовуєте bitsetдля цього? зазвичай це перекладається на long(в моїй реалізації iirc; так, наскільки марнотратний) або подібний інтегральний тип для кожного елемента, так чи інакше, чому б просто не використати ненав’язаних інтегралів? (або, зараз, constexprз нульовим сховищем)
підкреслити

[редагувати тайм-аут] ... але тоді я ніколи не розумів обґрунтування bitsetкласу, окрім того, що, здається, повторюється під час оточуючих дискусій про "тьфу", ми повинні приховати недобрі коріння низького рівня мови '
підкреслюй_d

" uint8змінні та параметри, ймовірно, не використовуватимуть менший стек, ніж ints" неправильно. Якщо у вас процесор з 8-бітовими регістрами, вам intпотрібно щонайменше 2 регістри, тоді як uint8_tпотрібно лише 1, тому вам знадобиться більше місця для стека, оскільки ви, швидше за все, не в регістрі (що також повільніше і може збільшити розмір коду ( залежно від набору інструкцій)). ( У вас є тип, він повинен бути uint8_tНЕ uint8)
+12431234123412341234123


5

Якщо можливо, НЕ використовуйте макроси. Їм не надто захоплюються, коли мова йде про сучасний C ++.


4
Правда. Те, що я сам ненавиджу щодо макросів, - це те, що ти не можеш наступати на них, якщо вони помиляються.
Карл

Я думаю, це щось, що можна було б зафіксувати у компіляторі.
celticminstrel

4

Переліки були б більш доречними, оскільки вони надають "значення для ідентифікаторів", а також безпеку типу. Ви можете чітко сказати, що "xDeleted" є "RecordType", і це означає "тип запису" (ух!) Навіть через роки. Конкурси вимагатимуть коментарів до цього, також вони вимагатимуть збільшення та зменшення коду.


4

З визначенням я втрачаю безпеку типу

Не обов'язково...

// signed defines
#define X_NEW      0x01u
#define X_NEW      (unsigned(0x01))  // if you find this more readable...

і з перерахунком я втрачаю деякий простір (цілі числа)

Не обов’язково - але ви маєте бути чіткими в місцях зберігання ...

struct X
{
    RecordType recordType : 4;  // use exactly 4 bits...
    RecordType recordType2 : 4;  // use another 4 bits, typically in the same byte
    // of course, the overall record size may still be padded...
};

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

Ви можете створити операторів, щоб усунути біль від цього:

RecordType operator|(RecordType lhs, RecordType rhs)
{
    return RecordType((unsigned)lhs | (unsigned)rhs);
}

З const я думаю, що я також втрачаю безпеку типу, оскільки випадкова uint8 може потрапити помилково.

Те саме може статися з будь-яким із цих механізмів: перевірка діапазону та значення зазвичай є ортогональною щодо безпеки типу (хоча типи, визначені користувачем - тобто ваші власні класи - можуть застосовувати "інваріантів" щодо своїх даних). З перерахунками, компілятор може вибрати тип більшого розміру для розміщення значень, а неініціалізована, пошкоджена або просто пропущена змінна enum може все-таки інтерпретувати її бітовий шаблон як число, якого ви не очікували - порівнюючи нерівне з будь-яким із ідентифікатори перерахування, будь-яку їх комбінацію та 0.

Чи є ще якийсь чистіший спосіб? / Якщо ні, то що б ви використовували і чому?

Ну, врешті-решт, перевірений довірений C-стиль бітовий АБО перерахування працює досить добре, як тільки у вас є зображення бітових полів та користувацьких операторів. Ви можете додатково покращити свою надійність за допомогою деяких спеціальних функцій перевірки та тверджень, як у відповіді mat_geek; методи часто однаково застосовні для обробки рядків, int, подвійних значень тощо.

Ви можете стверджувати, що це "чистіше":

enum RecordType { New, Deleted, Modified, Existing };

showRecords([](RecordType r) { return r == New || r == Deleted; });

Мені байдуже: біти даних упаковуються жорсткіше, але код значно зростає ... залежить від того, скільки об’єктів у вас є, а lamdbas - прекрасний такий, який вони є - все ще грізніший і складніше отримати правильно, ніж бітові АБО.

BTW / - аргумент щодо досить слабкого ІМХО щодо безпеки потоку - найкраще запам'ятовується як основний розгляд, а не стає домінуючою рушійною силою; обмін мютексом через бітові поля є більш імовірною практикою, навіть якщо вони не знають про їх упаковку (мютекси відносно об'ємні члени даних - я повинен бути дуже заклопотаний щодо продуктивності, щоб розглянути можливість наявності декількох мутексів для членів одного об’єкта, і я б уважно дивився достатньо, щоб помітити, що вони були бітовими полями). Будь-який тип підрозділу розміру слова може мати ту саму проблему (наприклад, a uint8_t). У будь-якому разі, ви можете спробувати операції зі стилем атомного порівняння та заміни, якщо ви відчайдушно бажаєте підвищити сумісність.


1
+1 штрафу. Але operator|слід вказати цілий тип ( unsigned int) перед інструкцією |. В іншому operator|випадку рекурсивно викличе себе і спричинить переповнення стека часу виконання. Я пропоную: return RecordType( unsigned(lhs) | unsigned(rhs) );. Ура
олібре

3

Навіть якщо вам потрібно використовувати 4 байти для зберігання перерахунків (я не такий знайомий із C ++ - я знаю, що ви можете вказати базовий тип у C #), все одно варто - використовувати enums.

У цей день і вік серверів з ГБ пам’яті такі речі, як 4 байти проти 1 байта пам’яті на рівні програми, взагалі не мають значення. Звичайно, якщо у вашій конкретній ситуації використання пам'яті є таким важливим (і ви не можете змусити C ++ використовувати байт, щоб підтримувати перерахунок), то ви можете розглянути маршрут "статичного const".

Зрештою, ви повинні запитати себе, чи варто досягти технічного обслуговування використання "статичного const" для 3-х байт економії пам'яті для вашої структури даних?

Ще щось, про що слід пам’ятати - IIRC, на x86 структури даних вирівняні в 4 байти, тому, якщо у вашій структурі «запису» ви не маєте декількох елементів ширини байт, це насправді може не мати значення. Перевірте і переконайтеся, що це робиться, перш ніж здійснити компроміс у ремонтопридатності для продуктивності / простору.


Ви можете вказати базовий тип у C ++, що стосується перегляду мови C ++ 11. До цього часу я вважаю, що "принаймні достатньо великого розміру для зберігання та використання в якості бітового поля для всіх заданих перелічувачів, але, мабуть, intякщо це занадто мало". [Якщо ви не вказали базовий тип у C ++ 11, він використовує застарілу поведінку. І навпаки, для enum classбазового типу C ++ 11 явно за замовчуванням intне вказано інше.]
Джастін Час -

3

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

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


Виглядає як надмірна кількість мого маленького додатка. але це здається хорошим рішенням.
Мілан Бабушков

2

Чи дійсно вам потрібно пройти навколо значень прапора як концептуальне ціле, або у вас буде багато коду прапора? Так чи інакше, я думаю, що мати це як клас чи структуру 1-бітових біт-полів може бути насправді зрозумілішим:

struct RecordFlag {
    unsigned isnew:1, isdeleted:1, ismodified:1, isexisting:1;
};

Тоді ваш клас записів міг би мати структурну змінну члена RecordFlag, функції можуть приймати аргументи типу struct RecordFlag і т. Д. Компілятор повинен упакувати бітові поля разом, економлячи місце.


Іноді в цілому, іноді як прапор. І мені також потрібно перевірити, чи встановлено певний прапор (коли я його передаю в цілому).
Мілан Бабушков

ну, якщо розділити, просто попросіть int. Коли разом, передайте структуру.
wnoise

Краще не буде. Доступ до бітових полів повільніше, ніж будь-що інше.
paercebal

Дійсно? Ви думаєте, що компілятор генерує істотно інший код для тестування бітових полів, ніж ручне роздвоєння бітів? І що це буде значно повільніше? Чому? Єдине, що ви не можете легко зробити так легко - це замаскувати кілька прапорів одночасно.
wnoise

Запускаючи простий тест читання, я отримую 5,50-5,58 секунди для біт-маскування проти 5,45-5,59 для доступу до бітового поля. Досить сильно не відрізняється.
wnoise

2

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

Але який би метод ви не використовували, щоб зрозуміти, що це значення, які є бітами, які можна об'єднати разом, використовуйте цей синтаксис для фактичних значень:

#define X_NEW      (1 << 0)
#define X_DELETED  (1 << 1)
#define X_MODIFIED (1 << 2)
#define X_EXISTING (1 << 3)

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


1
Для цього є прецедент, особливо у константах йоктл (). Я вважаю за краще використовувати шістнадцяткові константи, хоча: 0x01, 0x02, 0x04, 0x08, 0x10, ...
Джонатан Леффлер

2

На основі KISS , високої згуртованості та низької зв'язаності задайте ці питання -

  • Кому потрібно знати? мій клас, моя бібліотека, інші класи, інші бібліотеки, 3-ті сторони
  • Який рівень абстракції мені потрібно надати? Чи розуміє споживач розрядні операції.
  • Чи доведеться мати інтерфейс від VB / C # тощо?

Існує чудова книга " Крупномасштабний дизайн програмного забезпечення C ++ ", яка сприяє зовнішньому використанню базових типів, якщо ви можете уникнути іншої залежності файлового / інтерфейсного заголовка, яку слід спробувати.


1
а) 5-6 класи. б) тільки я, це проект для чоловіків; в) немає взаємодії
Мілан Бабушков,

2

Якщо ви використовуєте Qt, ви повинні шукати QFlags . Клас QFlags забезпечує безпечний для типу спосіб зберігання OR-комбінацій значень enum.


Ні, ні Qt. Насправді це проект wxWidgets.
Мілан Бабушков

0

Я б швидше пішов з

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

Просто тому, що:

  1. Він чистіший, і це робить код читабельним і доступним для обслуговування.
  2. Він логічно групує константи.
  3. Час програміста важливіше, якщо ви не збережете ці 3 байти.

Ну, я міг легко мати мільйон екземплярів класу Record, тому це може бути важливо. ОТО, це лише різниця між 1 МБ і 4 МБ, тому, можливо, я не повинен турбуватися.
Мілан Бабушков

@Vivek: Ви розглядали ціле обмеження ширини? Зокрема, перед C ++ 11.
user2672165

0

Не те, що мені подобається надмірно інженерно, але іноді в цих випадках може бути варто створити (малий) клас, щоб інкапсулювати цю інформацію. Якщо ви створюєте клас RecordType, він може мати такі функції, як:

пустота setDeleted ();

void clearDeleted ();

bool isDeleted ();

і т. д. ... (або що підходить до конвенції)

Це може підтвердити комбінації (у випадку, коли не всі комбінації є законними, наприклад, якщо "нові" та "видалені" не можуть бути встановлені одночасно). Якщо ви просто використовували бітові маски тощо, тоді код, який встановлює стан, повинен перевірити, клас також може інкапсулювати цю логіку.

Клас також може надавати вам можливість додавати змістовну інформацію журналу до кожного стану, ви можете додати функцію для повернення рядкового подання поточного стану тощо (або використовувати оператори потокового потоку '<<').

Незважаючи на те, що якщо ви турбуєтесь про зберігання, у вас все ще може бути клас, у якого є лише член "char" даних, тому візьміть лише невелику кількість пам’яті (припустимо, що це не віртуально). Звичайно, залежно від обладнання тощо ви можете мати проблеми з вирівнюванням.

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

Якщо ви виявите, що код, що використовує enum / # define / bitmask тощо, має багато коду «підтримки» для боротьби з недійсними комбінаціями, веденням журналу тощо, то, можливо, варто врахувати інкапсуляцію в класі. Звичайно, найчастіше прості проблеми краще вирішити простими рішеннями ...


На жаль, декларація повинна бути у .h файлі, оскільки вона використовується у проекті (використовується у 5-6 класах).
Мілан Бабушков
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.