11

假设我#define在外部库的头文件中有一个 s 列表。这些#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";
        ...
     ...
...

我有一种感觉,如果这是可能的,那么使用模板和元编程是可能的,但这只会起作用,错误代码实际上是一种类型,而不是一堆处理器宏。

4

8 回答 8

19

我通常以巨大的开关盒方式进行操作,尽管我通过以下方式使它变得更容易:

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

这是一个很好的问题,我很想看看人们有什么更好的方法

于 2010-04-16T04:48:33.960 回答
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];}

我更喜欢我已经提到的 switch case 方式,但是根据您的代码结构,作为构建过程的一部分,自动生成该数组可能更容易

于 2010-04-16T04:56:58.247 回答
4

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

于 2010-04-16T04:48:27.153 回答
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

于 2010-04-16T05:22:15.307 回答
1

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

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

于 2010-04-16T05:23:29.343 回答
1

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

enum{ NO_ERROR ONE_KIND_OF_ERROR ANOTHER_KIND_OF_ERROR }

这与您拥有的 s 完全相同#define,您可以将其放在命名空间中。现在您可以使用 Michael 的另一个涉及static const char* const error_names数组的答案,您可以按照您最初的要求进行操作。

于 2010-04-16T05:32:10.667 回答
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 语法,但它确实简化了写作。

于 2010-04-16T08:30:31.157 回答
0

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

于 2010-04-16T04:52:58.197 回答