简体   繁体   中英

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. 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.

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.

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. The properties are implemented in different ways in C++ though: Some have getter/setter methods, some are set in a special struct. Scripting actions reference C++ objects casted to a common base class. 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. Also, this has to work on an embedded compiler, so boost or C++11 are (sadly) not available.

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:

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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