Macro magic to combine ads and implementations

    One of the unpleasant problems is that when you make 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 copy or comparison operators are redefined, then they too. 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, difficult to reproduce problems).

    Usually, if a class involves active modification of fields, then a macro is written that takes the implementation of actions with the field (initialization, copying, serialization, reflection) onto itself.
    As a result, the variable should be registered in only two places - declared in the class and implemented (or registered for subsequent 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 don’t cite the implementation of macros, for example, registration of pointers to data fields, their names and initial values ​​for subsequent use.

    Another simpler example. It is necessary to make a reflection of the enumeration, for example, to match the variant of the enumeration with a string of its name. Usually this is 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 Register implementation will provide the reader’s imagination.

    For quite some time I was content with this state of affairs. But recently I thought what could be done better, having managed with a single announcement. You can do this using all the same macros.

    For the last example, it will 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)))
    }
    

    Macros DECLARE_MODEL_TYPE unfold first in the elements listed, then the code of END_MODEL_TYPE close the transfer unit and insert the header function on in the function body insert a call Register for the elements, but in reverse order, and finally the brace will close the function block (so it is without a semicolon )
    Similar code can be written for class fields.

    It remains only to say about the shortcomings:
    • registration in the reverse order, but if the order is still important, you can take this into account in the Register implementation , for example, add the next field to the top of the list, and not to the end;
    • problems with systems for automatic generation of documentation like DOxygen, they will not guess to deploy macros;
    • the need to add another closing bracket after END_MODEL_TYPE when adding a field . Unpleasant drawback, because you still have to edit the code in two places. A little happy that the preprocessor will not allow to forget about the bracket.

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

    Also popular now: