📜 ⬆️ ⬇️

Macro magic for combining ad and implementation

One of the unpleasant problems is that when making even simple changes, you have to edit the code in several places. For example, if a data field is added to a class, it must be added to the class declaration, initialized in the constructor (s), and if the copy or comparison operators are redefined, so are they. This takes time and leads to errors, if you forget about one of the places (it is especially unpleasant to forget to initialize, such errors can live for years, and cause unexpected, hard-to-reproduce problems).

Usually, if a class assumes active modification of fields, then a macro is written that takes the implementation of field actions (initialization, copying, serialization, reflection) onto itself.
As a result, the variable must be registered in only two places - declared in the class and implemented (or registered for later use in the implementation).

It turns out something like:
class TData { public: int Number; float Factor; BEGIN_FIELDS FIELD(Number, 0) FIELD(Factor, 1.0f) END_FIELDS }; 

I do not provide implementation of macros, for example, it can be registration of pointers to data fields, their names and initial values ​​for later use.

Another example, simpler. You need to make a reflection of the enumeration, for example, to associate with the enumeration variant a string of its name. This is usually done something like this:
 enum TModelType { Car, Weapon, Human }; #define REFLECT_MODEL_TYPE(mac_value) Register(mac_value, #mac_value); void TModelTypeReflection::RegisterTypes() { REFLECT_MODEL_TYPE(Car) REFLECT_MODEL_TYPE(Weapon) REFLECT_MODEL_TYPE(Human) } 

The TModelTypeReflection declaration and the Register implementation will provide the reader with imagination.
')
For a long time I was content with this state of affairs. But recently I thought that you can do better, bypassing the only announcement. This can be done using all the same macros.

For the last example, it would look like this:
 #define DECLARE_MODEL_TYPE(mac_value, mac_next) \ mac_value, \ mac_next \ Register(mac_value, #mac_value); #define END_MODEL_TYPE \ }; void TModelTypeReflection::RegisterTypes() { enum TModelType { DECLARE_MODEL_TYPE(Car, DECLARE_MODEL_TYPE(Weapon, DECLARE_MODEL_TYPE(Human, END_MODEL_TYPE))) } 

The DECLARE_MODEL_TYPE macros are expanded first into the enumeration elements, then the code from END_MODEL_TYPE closes the enumeration block and inserts the function header, then the Register calls for the elements are inserted in the function body, only in reverse order, and finally the curly bracket closes the function block (therefore, it also has ).
Similar code can be written for class fields.

It remains only to say about the shortcomings:

An alternative solution that allows combining an advertisement with an implementation is the use of a code generator, but this approach also has its drawbacks.

Source: https://habr.com/ru/post/148494/


All Articles