简体   繁体   English

使用预处理器对C / C ++进行元编程

[英]Metaprogramming C/C++ using the preprocessor

So I have this huge tree that is basically a big switch/case with string keys and different function calls on one common object depending on the key and one piece of metadata. 所以我有这个巨大的树,基本上是一个大的开关/案例,带有字符串键和一个公共对象上的不同函数调用,具体取决于键和一个元数据。

Every entry basically looks like this 每个条目基本上都是这样的

} else if ( strcmp(key, "key_string") == 0) {
    ((class_name*)object)->do_something();
} else if ( ...

where do_something can have different invocations, so I can't just use function pointers. do_something可以有不同的调用,所以我不能只使用函数指针。 Also, some keys require object to be cast to a subclass. 此外,某些键需要将对象强制转换为子类。

Now, if I were to code this in a higher level language, I would use a dictionary of lambdas to simplify this. 现在,如果我用更高级别的语言编写代码,我会使用lambdas字典来简化这一过程。

It occurred to me that I could use macros to simplify this to something like 在我看来,我可以使用宏来简化这类

case_call("key_string", class_name, do_something());
case_call( /* ... */ )

where case_call would be a macro that would expand this code to the first code snippet. 其中case_call是一个宏,可以将此代码扩展到第一个代码片段。

However, I am very much on the fence whether that would be considered good style. 然而,我非常关注这是否会被视为好风格。 I mean, it would reduce typing work and improve the DRYness of the code, but then it really seems to abuse the macro system somewhat. 我的意思是,它会减少打字工作并改善代码的干燥度,但它确实似乎在某种程度上滥用了宏系统。

Would you go down that road, or rather type out the whole thing? 你会走那条路,还是整理出整条路? And what would be your reasoning for doing so? 你这样做的原因是什么?

Edit 编辑

Some clarification: 一些澄清:

This code is used as a glue layer between a simplified scripting API which accesses several different aspects of a C++ API as simple key-value properties. 此代码用作简化脚本API之间的粘合层,该API作为简单的键值属性访问C ++ API的几个不同方面。 The properties are implemented in different ways in C++ though: Some have getter/setter methods, some are set in a special struct. 这些属性在C ++中以不同的方式实现:有些具有getter / setter方法,有些是在特殊结构中设置的。 Scripting actions reference C++ objects casted to a common base class. 脚本操作引用C ++对象转换为公共基类。 However, some actions are only available on certain subclasses and have to be cast down. 但是,某些操作仅适用于某些子类,必须进行转换。

Further down the road, I may change the actual C++ API, but for the moment, it has to be regarded as unchangeable. 接下来,我可能会更改实际的C ++ API,但目前,它必须被视为不可更改。 Also, this has to work on an embedded compiler, so boost or C++11 are (sadly) not available. 此外,这必须在嵌入式编译器上工作,因此(遗憾的是)boost或C ++ 11不可用。

That seems to me an appropriate use of macros. 在我看来,适当使用宏。 They are, after all, made for eliding syntactic repetition. 毕竟,它们是为了避免语法重复而制作的。 However, when you have syntactic repetition, it's not always the fault of the language—there are probably better design choices out there that would let you avoid this decision altogether. 然而,当你有语法重复时,它并不总是语言的错误 - 可能有更好的设计选择可以让你完全避免这个决定。

The general wisdom is to use a table mapping keys to actions: 一般的智慧是使用表映射键来执行操作:

std::map<std::string, void(Class::*)()> table;

Then look up and invoke the action in one go: 然后查找并一次调用该操作:

object->*table[key]();

Or use find to check for failure: 或使用find检查失败:

const auto i = table.find(key);
if (i != table.end())
    object->*(i->second)();
else
    throw std::runtime_error(...);

But if as you say there is no common signature for the functions (ie, you can't use member function pointers) then what you actually should do depends on the particulars of your project, which I don't know. 但是如果你说没有函数的共同签名(即你不能使用成员函数指针)那么你实际上该做什么取决于你不知道的项目细节。 It might be that a macro is the only way to elide the repetition you're seeing, or it might be that there's a better way of going about it. 可能是一个宏是唯一能够忽视你所看到的重复的方法,或者可能是一种更好的方式来实现它。

Ask yourself: why do my functions take different arguments? 问问自己: 为什么我的函数采用不同的论点? Why am I using casts? 我为什么要使用演员表? If you're dispatching on the type of an object, chances are you need to introduce a common interface. 如果您要调度对象的类型,则可能需要引入通用接口。

I would suggest you slightly reverse the roles. 我建议你稍微改变角色。 You are saying that the object is already some class that knows how to handle a certain situation, so add a virtual void handle(const char * key) in your base class and let the object check in the implementation if it applies to it and do whatever is necessary. 你说这个对象已经是一个知道如何处理某种情况的类了,所以在你的基类中添加一个virtual void handle(const char * key) ,让对象检查实现是否适用于它并做什么是必要的。

This would not only eliminate the long if-else-if chain, but would also be more type safe and would give you more flexibility in handling those events. 这不仅可以消除长if-else-if链,而且还可以更安全,并且可以更灵活地处理这些事件。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM