可以将#defines列表转换为字符串

时间:2010-04-16 04:45:01

标签: c++ string c-preprocessor literals

假设我在外部库的头文件中有一个#define列表。这些#define表示从函数返回的错误代码。我想编写一个转换函数,它可以将错误代码作为输入,并作为输出返回表示实际#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 个答案:

答案 0 :(得分:19)

我通常使用巨型开关盒的方式,虽然我使用它更容易:

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

这是一个很好的问题,我很想知道人们有更好的方式

答案 1 :(得分:6)

在生成的代码中流行的另一种方法是:

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

我更喜欢切换案例方式already mentioned,但是根据代码的结构,在构建过程中自动生成该数组可能会更容易

答案 2 :(得分:4)

你是对的。没有办法在运行时恢复预处理器定义的标识符(除非你可以阅读源代码,但这是作弊)。你最好创建一个常量的名称数组并用错误代码索引它(当然还有正确的边界检查) - 编写一个脚本来生成它应该很容易。

答案 3 :(得分:2)

看一下boost预处理器。 您可以创建列表/数组/代码对序列:

#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

答案 4 :(得分:1)

这里的一种可能性是编写一个小程序来解析包含#defines的.h文件,并为convertToString()函数发出相应的源代码。然后,只要.h文件发生更改,您就可以将该程序自动作为构建过程的一部分运行。这是一个更多的工作,但一旦实现,你将永远不再需要手动更新你的int< - >字符串转换函数。

如果要支持使用相同常量的多种语言的代码,这将非常有用。例如,在我的一个应用程序中,更改我的#defines头文件会导致相应的C ++,Python和Java文件被自动重新生成。这使项目维护更容易,更不容易出错。

答案 5 :(得分:1)

如果你一定想保留#define,我会选择迈克尔优雅#define STR(code)的答案。但是定义比C ++更多C,定义的最大缺点是你不能将它们放在命名空间中。它们将污染您包含它们的任何程序的全局命名空间。如果您有权更改它,我建议使用匿名枚举:

enum{ NO_ERROR ONE_KIND_OF_ERROR ANOTHER_KIND_OF_ERROR }

这与您拥有的#define完全相同,您可以将其放在命名空间中。现在你可以使用迈克尔的另一个涉及static const char* const error_names数组的答案,你可以做你最初提出的问题。

答案 6 :(得分:1)

你实际上可以两种方式,即有两个函数,从代码转换为错误来回。

第一件事当然是#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;
  }
}

免责声明:我对lambda语法不太确定,但确实简化了写作。

答案 7 :(得分:0)

#define FOO 1由预处理器处理为简单的文本替换。如果要保留这些定义,则需要巨型开关。