Возможный преобразовать список #defines в строки

Предположим, что у меня есть список #defines в заголовочном файле для внешней библиотеки. Они #defines представляют коды ошибок, возвращенные из функций. Я хочу записать функцию преобразования, которая может взять в качестве входа код ошибки и возвратиться как вывод строковый литерал, представляющий фактическое #define имя.

Как пример, если я имею

#define NO_ERROR 0
#define ONE_KIND_OF_ERROR 1
#define ANOTHER_KIND_OF_ERROR 2

Я хотел бы, чтобы функция смогла к названному как

int errorCode = doSomeLibraryFunction();
if (errorCode)
    writeToLog(convertToString(errorCode));

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

const char* convertToString(int errorCode)
{
    switch (errorCode)
    {
        case NO_ERROR:
           return "NO_ERROR";
        case ONE_KIND_OF_ERROR:
           return "ONE_KIND_OF_ERROR";
        ...
     ...
...

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

11
задан Brian Tompsett - 汤莱恩 27 November 2015 в 15:40
поделиться

8 ответов

Обычно я делаю это с помощью гигантского коммутатора, хотя я немного упрощаю его:

#define STR(code) case code: return #code
switch (errorCode)
{
    STR(NO_ERROR);
    STR(ONE_KIND_OF_ERROR);
}

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

19
ответ дан 3 December 2019 в 02:10
поделиться

Вы правы. Невозможно восстановить определенные препроцессором идентификаторы во время выполнения (если вы не можете прочитать источник, но это обман). Лучше создать постоянный массив имен и проиндексировать его с кодом ошибки (с правильной проверкой границ, конечно) - он должен будет довольно легко написать сценарий для его генерации.

4
ответ дан 3 December 2019 в 02:10
поделиться

взгляните на препроцессор ускорения. Вы можете создать список / массив / последовательность пар кодов:

#define codes ((1,"code1"))((...))

#define code1 1
...

// then use preprocessor FOR_EACH to generate error handlers

соответствующая ссылка:

http://www.boost.org/doc/libs/ 1_41_0 / libs / preprocessor / doc / ref / seq_for_each.html

http://www.boost.org/doc/libs/1_41_0/libs/preprocessor/doc/ref/tuple_elem.html

2
ответ дан 3 December 2019 в 02:10
поделиться

Одна из возможностей здесь - написать небольшую программу, которая анализирует файл .h, содержащий #defines, и генерирует соответствующий исходный код для функции convertToString (). Затем вы можете автоматически запускать эту программу как часть процесса сборки при изменении файла .h. Это немного больше работы, но как только она будет реализована, вам больше никогда не придется вручную обновлять функцию преобразования строк int <->.

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

1
ответ дан 3 December 2019 в 02:10
поделиться

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

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

Вы можете сделать это по-другому, используя пространства имен для изоляции символов и предварительную обработку для обработки всего поколения. Для поиска мы будем использовать Bimap .

Сначала нам нужно определить класс Handler (не обязательно встраивать все это, но это проще для примеров)

#include <boost/bimap.hpp>
#include <boost/optional.hpp>

namespace error
{

  class Handler
  {
  public:
    typedef boost::optional<int> return_code;
    typedef boost::optional<std::string> return_description;

    static bool Register(int code, const char* description)
    {
      typedef error_map::value_type value_type;
      bool result = MMap().insert(value_type(code,description)).second;

      // assert(result && description)
      return result;
    }

    static return_code GetCode(std::string const& desc)
    {
      error_map::map_by<description>::const_iterator it
          = MMap().by<description>().find(desc);
      if (it != MMap().by<description>().end()) return it->second;
      else return return_code();
    }

    static return_description GetDescription(int c)
    {
      error_map::map_by<code>::const_iterator it
          = MMap().by<code>().find(c);
      if (it != MMap().by<code>().end()) return it->second;
      else return return_description();
    }

    typedef std::vector< std::pair<int,std::string> > errors_t;
    static errors_t GetAll()
    {
      errors_t result;
      std::for_each(MMap().left.begin(), MMap().left.end(),
                    result.push_back(boost::lambda::_1));
      return result;
    }

  private:
    struct code {};
    struct description {};

    typedef boost::bimap<
      boost::tagged<int, code>,
      boost::tagged<std::string, description>
    > error_map;

    static error_map& Map() { static error_map MMap; return MMap; }
  };

  // Short-Hand
  boost::optional<int> GetCode(std::string const& d)
  {
    return Handler::GetCode(d);
  }

  boost::optional<std::string> GetDescription(int c)
  { 
    return Handler::GetDescription(c);
  }
} // namespace error

Затем нам просто нужно добавить немного синтаксического сахара:

#define DEFINE_NEW_ERROR(Code_, Description_)            \
  const int Description_ = Code_;                        \
  namespace error {                                      \
    const bool Description##_Registered =                \
      ::error::Handler::Register(Code_, #Description_);  \
  }

Мы могли бы быть немного более жестокими в случае, если регистрации неизвестной ошибки (например, assert).

И затем мы всегда можем превратить этот макрос в макрос, который может определять несколько символов за один раз ... но это оставлено как упражнение.

Использование:

// someErrors.hpp
#include "error/handler.hpp"

DEFINE_NEW_ERROR(1, AnError)
DEFINE_NEW_ERROR(2, AnotherError)

// someFile.cpp
#include <iostream>
#include "error/handler.hpp"

int main(int argc, char* argv[])
{
  int code = 6;
  boost::optional<std::string> desc = error::GetDescription(code);

  if (desc)
  {
    std::cout << "Code " << code << " is mapped to <" << *desc << ">" << std::endl;
  }
  else
  {
    std::cout << "Code " << code << " is unknown, here is the list:\n";

    ::error::Handler::errors_t errors = ::Error::Handler::GetAll();

    std::for_each(errors.begin(), errors.end(), std::cout << "  " << _1);

    std::cout << std::endl;
  }
}

Отказ от ответственности: я не слишком уверен в синтаксисе лямбда, но он действительно упростил написание.

1
ответ дан 3 December 2019 в 02:10
поделиться

#define FOO 1 обрабатывается препроцессором как простая замена текста. Если вы хотите сохранить эти определения, вам нужен гигантский переключатель.

0
ответ дан 3 December 2019 в 02:10
поделиться

Если вы определенно хотите сохранить #define , я бы выбрал элегантный #define STR (код) Майкла. ответ. Но определения - это больше C, чем C ++, и большой недостаток определений заключается в том, что вы не можете поместить их в пространство имен. Они будут загрязнять глобальное пространство имен любой программы, в которую вы их включаете. Если в ваших силах изменить это, я бы рекомендовал вместо этого использовать анонимное перечисление:

enum{ NO_ERROR ONE_KIND_OF_ERROR ANOTHER_KIND_OF_ERROR }

Это точно так же, как #define у вас есть, и вы можете поместить его в пространство имен. И теперь вы можете использовать другой ответ Майкла, связанный с массивом static const char * const error_names , чтобы сделать то, что вы изначально просили.

1
ответ дан 3 December 2019 в 02:10
поделиться

Другой способ сделать это, популярный в сгенерированном коде:

#define NO_ERROR 0
#define ONE_KIND_OF_ERROR 1
#define ANOTHER_KIND_OF_ERROR 2
static const char* const error_names[] = {"NO_ERROR", "ONE_KIND_OF_ERROR", "ANOTHER_KIND_OF_ERROR"};

const char* convertToString(int errorCode) {return error_names[errorCode];}

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

6
ответ дан 3 December 2019 в 02:10
поделиться
Другие вопросы по тегам:

Похожие вопросы: