Должен ли я использовать перечисление или несколько констант для непоследовательных констант в C ++? - PullRequest
4 голосов
/ 17 сентября 2010

Я пишу портирование набора функций file-io из c в класс c ++. «Магические числа» (безымянные константы) имеются в большом количестве.

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

Несколько лет назад один из опытных программистов научил меня тому, что использование «магических чисел» по своей сути является злом, и поэтому я с тех пор стараюсь избегать использования неназванных констант в моем порте. Поэтому я хочу создать какой-то список констант, где хранятся записи.

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

Могу ли я безопасно использовать любое из этих решений? Есть ли какие-то преимущества перед одним из них?

, например
ВАРИАНТ 1

namespace hdr_pos {
   const unsigned int item_1_pos=4;
   const unsigned int item_2_pos=8;
   const unsigned int item_3_pos=12;
   const unsigned int item_4_pos=24;
   const unsigned int item_5_pos=32;
};

ВАРИАНТ 2

namespace hdr_pos {
   enum e {
      item_1_pos=4,
      item_2_pos=8,
      item_3_pos=12,
      item_4_pos=24,
      item_5_pos=32
   };
};

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

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

Примечание: Я бы использовал более описательные имена, конечно, в моей реальной реализации; Я просто назвал вещи штукой _ <#> _... для примера ...

Ответы [ 5 ]

2 голосов
/ 17 сентября 2010

Я вижу два преимущества использования enum. Во-первых, некоторые отладчики могут переводить константы обратно в имена переменных перечисления (что в некоторых случаях может облегчить отладку). Кроме того, вы можете объявить переменную перечислимого типа, которая может содержать только значение из этого перечисления. Это может дать вам дополнительную форму проверки типов, которую вы бы не использовали, просто используя константы.

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

1 голос
/ 18 сентября 2010

Я имел дело с этой ситуацией ранее, для кодов ошибок.

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

  1. , которые вы можете назначитьint для перечисления, которое не соответствует ни одному значению (слишком плохо)
  2. само значение объявлено в заголовке, что означает, что переназначение кода ошибки (это происходит ...) нарушает совместимость кода, вытакже нужно соблюдать осторожность при добавлении элементов ...
  3. Вы должны определить все коды в одном и том же заголовке, даже если часто какой-то код естественным образом ограничивается небольшой частью приложения, потому что перечисления не могут быть "расширенный "
  4. нет проверки того, что один и тот же код не назначен дважды
  5. вы не можете перебирать различные поля enum

При разработке моегоРешение с помощью кодов ошибок, таким образом, я выбрал другой путь: константы в пространстве имен, определенные в исходных файлах, которые обращаются к пунктам 2 и 3. Хотя для обеспечения безопасности типов, константы не int, аопределенный Code класс:

namespace error { class Code; }

Тогда я могу определить несколько файлов ошибок:

// error/common.hpp

namespace error
{
  extern Code const Unknown;
  extern Code const LostDatabaseConnection;
  extern Code const LostNASConnection;
}

// error/service1.hpp
// error/service2.hpp

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

Однако я думал об этомсделав требуемый конструктор частным и принудительно применяя конструктор, мы даже сразу получим 4. и 5.:

// error/code.hpp

namespace error
{
  class Code;

  template <size_t constant> Code const& Make(); // not defined here

  class Code: boost::totally_ordered<Code>
  {
  public:
    Code(): m(0) {} // Default Construction is useful, 0 is therefore invalid

    bool operator<(Code const& rhs) const { return m < rhs.m; }
    bool operator==(Code const& rhs) const { return m == rhs.m; }

  private:
    template <size_t> friend Code const& Make();
    explicit Code(size_t c): m(c) { assert(c && "Code - 0 means invalid"); }

    size_t m;
  };

  std::set<Code> const& Codes();
}


// error/privateheader.hpp (inaccessible to clients)

namespace error
{
  std::set<Code>& PrivateCodes() { static std::set<Code> Set; return Set; }

  std::set<Code> const& Codes() { return PrivateCodes(); }

  template <size_t constant>
  Code const& Make()
  {
    static std::pair< std::set<Code>::iterator, bool > r
      = PrivateCodes().insert(Code(constant));
    assert(r.second && "Make - same code redeclared");
    return *(r.first);
  }
}

//
// We use a macro trick to create a function whose name depends
// on the code therefore, if the same value is assigned twice, the
// linker should complain about two functions having the same name
// at the condition that both are located into the same namespace
//
#define MAKE_NEW_ERROR_CODE(name, value)         \
  Make<value>(); void _make_new_code_##value ();


// error/common.cpp

#include "error/common.hpp"

#include "privateheader.hpp"

namespace error
{
  Code const Unkown = MAKE_NEW_ERROR_CODE(1)
  /// ....
}

чуть больше работы (для фреймворка),и только проверка времени соединения / выполнения одной и той же проверки назначений.Хотя дубликаты легко диагностировать, просто отсканировав шаблон MAKE_NEW_ERROR_CODE

Веселитесь!

0 голосов
/ 17 сентября 2010

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

0 голосов
/ 17 сентября 2010

Следует иметь в виду, что вы не можете взять адрес enum:

const unsigned* my_arbitrary_item = &item_1_pos;

0 голосов
/ 17 сентября 2010

Название вашего вопроса предполагает, что основная причина, по которой у вас есть сомнения относительно использования enum, заключается в том, что ваши константы не итеративны .Но в C ++ типы перечислений уже не повторяются.Вы должны перепрыгнуть через несколько обручей, чтобы создать итеративный тип enum.

Я бы сказал, что если ваши константы связаны по своей природе, то enum - это довольно хорошая идея, независимо от того, являются ли константы итеративными или нет.Однако основным недостатком перечислений является полное отсутствие контроля типов.Во многих случаях вы можете предпочесть иметь строгий контроль над типами ваших константных значений (например, иметь их без знака), и с этим enum не может помочь (по крайней мере, пока).

...