假设我在外部库的头文件中有一个#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";
...
...
...
我有一种感觉,如果这是可能的,那么可以使用模板和元编程,但这只会使错误代码实际上是一种类型而不是一堆处理器宏。
答案 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
由预处理器处理为简单的文本替换。如果要保留这些定义,则需要巨型开关。