探讨如何获取一个宏对应的字符串,或整数值对应的宏名称字符串。最后提供了一个轻量级的helper类实现这种机制。
问题
假设定义了下面这些宏:
#define ZERO 0
#define ONE 1
#define TWO 2
代码中在打印日志的时候,希望把这些整数(0、1、2)对应的宏的名称打印出来,即”ZERO”, “ONE”, “TWO”这种字符串。
实现a
可以借助预处理的#语法:
//M2S: Macro to String
#define M2S(x) #x
使用示例:
void foo(int v)
{
switch (v) {
case ZERO:
std::cout<< M2S(ZERO) <<std::endl;
break;
case ONE:
std::cout<< M2S(ONE) <<std::endl;
break;
case TWO:
std::cout<< M2S(TWO) <<std::endl;
break;
default:
std::cout<<"Unknown"<<std::endl;
}
}
int main()
{
for (int i = 0; i < MAX + 1; i++) {
foo(i);
}
return 0;
}
运行结果:
ZERO
ONE
TWO
Unknown
注:因为#的行为是属于预编译的一部分,所以下面的做法是行不通的:
std::cout<< #ZERO << std::endl;
问题
在foo中的std::cout调用可否消除重复,只用一行呢?即:
void foo(int v)
{
if (v >= ZERO && v <= TWO) {
std::cout<< M2S(v) <<std::endl;
} else {
std::cout<<"Unknown"<<std::endl;
}
}
此时运行结果:
v
v
v
Unknown
显然,这并不是我们想要的结果。为此,可以借助下面的一种方法。
实现b
// see: http://gcc.gnu.org/onlinedocs/cpp/Stringification.html
#include <iostream>
#include <map>
#define ZERO 0
#define ONE 1
#define TWO 2
#define MAX 3
//M2S: Macro to String
#define M2S(x) #x
struct Bar {
int i;
const char* name;
};
//VNP: Value-Name Pair
#define VNP(x) {x, #x}
struct Bar values[] = {
VNP(ZERO),
VNP(ONE),
VNP(TWO)
};
const char* GetName(int i)
{
if (i >= 0 && i < MAX) return values[i].name;
return "Unknown";
};
int main()
{
for (int i = 0; i < MAX + 1; i++) {
std::cout<<i<<": "<<GetName(i)<<std::endl;
}
return 0;
}
运行结果:
0: ZERO
1: ONE
2: TWO
3: Unknown
请按任意键继续. . .
模块化
因为以上的使用场景是常见的,且一个项目中可能为多个宏定义组做这个事情,因此考虑封装。
代码
新建一个(比如)MacroName的class。
头文件:
#ifndef __MACRO_NAME_H
#define __MACRO_NAME_H
#include <map>
//VNP: Value-Name Pair
#define VNP(x) x, #x
extern const char* UNKNOWN;
class MacroName {
public:
void Add(int v, const char* name);
const char* GetName(int v) const;
private:
typedef std::map<int, const char*> VNP_Type;
VNP_Type m_values;
};
#endif
实现文件:
#include "MacroName.h"
const char* UNKNOWN = "Unknown";
void MacroName::Add(int v, const char* name)
{
m_values.insert(std::make_pair(v, name));
}
const char* MacroName::GetName(int v) const
{
VNP_Type::const_iterator i = m_values.find(v);
if (i != m_values.end()) return i->second;
return UNKNOWN;
}
测试文件:
// see: http://gcc.gnu.org/onlinedocs/cpp/Stringification.html
#include <iostream>
#include "MacroName.h"
#define ZERO 0
#define ONE 1
#define TWO 2
#define MAX 3
//M2S: Macro to String
#define M2S(x) #x
void init(MacroName &mn)
{
mn.Add(VNP(ZERO)); //mn.Add(ZERO, M2S(ZERO));
mn.Add(VNP(ONE));
mn.Add(VNP(TWO));
}
int main()
{
MacroName mn;
init(mn);
for (int i = 0; i < MAX + 1; i++) {
std::cout<<i<<": "<<mn.GetName(i)<<std::endl;
}
return 0;
}
运行结果和之前一样,符合预期。
尽管如此,对于mn.Add(VNP(ONE));的调用似乎有待优化,因为这里多了一个VNP。——显然是受之前代码的影响而设计出来的。
下面是一种优化:
头文件:
#ifndef __MACRO_NAME_H
#define __MACRO_NAME_H
#include <map>
#define AddMacro(obj, macro) obj.Add(macro, #macro)
extern const char* UNKNOWN;
class MacroName {
public:
void Add(int v, const char* name);
const char* GetName(int v) const;
private:
typedef std::map<int, const char*> VNP_Type;
VNP_Type m_values;
};
#endif
即增加了一句://删除了不再使用的VNP宏
#define AddMacro(obj, macro) obj.Add(macro, #macro)
调用示例:
// see: http://gcc.gnu.org/onlinedocs/cpp/Stringification.html
#include <iostream>
#include "MacroName.h"
#define ZERO 0
#define ONE 1
#define TWO 2
#define MAX 3
void init(MacroName &mn)
{
AddMacro(mn, ZERO);
AddMacro(mn, ONE);
AddMacro(mn, TWO);
}
int main()
{
MacroName mn;
init(mn);
for (int i = 0; i < MAX + 1; i++) {
std::cout<<i<<": "<<mn.GetName(i)<<std::endl;
}
return 0;
}
在init中,把面向对象的调用方式,改成了普通的函数调用方式AddMacro。——这可能也是不太习惯的地方。但因为#获取宏名称字符串只能发生在预编译期,所以我们总不能在MacroName的成员函数中做这个事情,因为即便传入宏到成员函数中,其传入行为在(编译&链接之后)执行期,所以已经把宏转换成整数值了。