Возможно преобразовать список #defines в строки - PullRequest
11 голосов
/ 16 апреля 2010

Предположим, у меня есть список #define в заголовочном файле для внешней библиотеки. Эти #define s представляют коды ошибок, возвращаемые функциями. Я хочу написать функцию преобразования, которая может принимать на вход код ошибки и возвращать в качестве вывода строковый литерал, представляющий фактическое #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";
        ...
     ...
...

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

Ответы [ 8 ]

19 голосов
/ 16 апреля 2010

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

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

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

6 голосов
/ 16 апреля 2010

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

#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];}

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

4 голосов
/ 16 апреля 2010

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

2 голосов
/ 16 апреля 2010

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

#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

1 голос
/ 16 апреля 2010

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

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

Вы можете сделать это другим, хотяиспользование пространств имен для выделения символов и предварительная обработка для обработки всего поколения.Для части поиска мы будем использовать 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_);  \
  }

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

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

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

// 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;
  }
}

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

1 голос
/ 16 апреля 2010

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

enum{ NO_ERROR ONE_KIND_OF_ERROR ANOTHER_KIND_OF_ERROR }

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

1 голос
/ 16 апреля 2010

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

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

0 голосов
/ 16 апреля 2010

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

...