简体   繁体   中英

Expansion of Nested Macros with reinterpret cast in c++

I stumbled across the below code and really found it complex to understand the nested macro and type casting in it.

Also when i tried to compile the code , i have encountered an error

Would need an explanantion of the below code.

why BEGIN_STATE_MAP and END_STATE_MAP set as labels in Motor.h , This is really new to me

Thanks in advance

Motor.h

// the Motor state machine class
class Motor : public StateMachine
{
public:
    Motor() : StateMachine(ST_MAX_STATES) {}

    // external events taken by this state machine
    void Halt();
    void SetSpeed(MotorData*);
private:
    // state machine state functions
    void ST_Idle();
    void ST_Stop();
    void ST_Start(MotorData*);
    void ST_ChangeSpeed(MotorData*);

    // state map to define state function order
    BEGIN_STATE_MAP
        STATE_MAP_ENTRY(ST_Idle)
        STATE_MAP_ENTRY(ST_Stop)
        STATE_MAP_ENTRY(ST_Start)
        STATE_MAP_ENTRY(ST_ChangeSpeed)
    END_STATE_MAP

    // state enumeration order must match the order of state
    // method entries in the state map
    enum E_States {
        ST_IDLE = 0,
        ST_STOP,
        ST_START,
        ST_CHANGE_SPEED,
        ST_MAX_STATES
    };
};
#endif //MOTOR_H

what are BEGIN_STATE_MAP and END_STATE_MAP, This definition i found i really new, BEGIN_STATE_MAP and END_STATE_MAP are the Macros defined in the below header file.

StateMachine.h

#ifndef STATE_MACHINE_H
#define STATE_MACHINE_H
#include <stdio.h>
#include "EventData.h"

struct StateStruct;

// base class for state machines
class StateMachine
{
public:
    StateMachine(int maxStates);
    virtual ~StateMachine() {}
protected:
    enum { EVENT_IGNORED = 0xFE, CANNOT_HAPPEN };
    unsigned char currentState;
    void ExternalEvent(unsigned char, EventData* = NULL);
    void InternalEvent(unsigned char, EventData* = NULL);
    virtual const StateStruct* GetStateMap() = 0;
private:
    const int _maxStates;
    bool _eventGenerated;
    EventData* _pEventData;
    void StateEngine(void);
};

typedef void (StateMachine::*StateFunc)(EventData *);
struct StateStruct
{
    StateFunc pStateFunc;
};

#define BEGIN_STATE_MAP \
public:\
const StateStruct* GetStateMap() {\
    static const StateStruct StateMap[] = {

#define STATE_MAP_ENTRY(entry)\
    { reinterpret_cast<StateFunc>(entry) },

#define END_STATE_MAP \
    { reinterpret_cast<StateFunc>(NULL) }\
    }; \
    return &StateMap[0]; }

#define BEGIN_TRANSITION_MAP \
    static const unsigned char TRANSITIONS[] = {\

#define TRANSITION_MAP_ENTRY(entry)\
    entry,

#define END_TRANSITION_MAP(data) \
    0 };\
    ExternalEvent(TRANSITIONS[currentState], data);

#endif

EventData.h

#ifndef EVENT_DATA_H
#define EVENT_DATA_H

class EventData
{
public:
   virtual ~EventData() {};
};
#endif //EVENT_DATA_H

While i tried to compile the code above.Below is the error that was encountered

Error

-------------- Build: Debug in StateMachine (compiler: GNU GCC Compiler)---------------

mingw32-g++.exe -Wall -fexceptions -g -pedantic -Wzero-as-null-pointer-constant -std=c++0x -Wextra -Wall  -c C:\Users\xprk569\StateMachine\main.cpp -o obj\Debug\main.o
In file included from C:\Users\xprk569\StateMachine\main.cpp:2:0:
C:\Users\xprk569\StateMachine\Motor.h: In member function 'virtual const StateStruct* Motor::GetStateMap()':
C:\Users\xprk569\StateMachine\StateMachine.h:40:40: error: invalid use of member (did you forget the '&' ?)
     { reinterpret_cast<StateFunc>(entry) },
                                        ^
C:\Users\xprk569\StateMachine\Motor.h:29:9: note: in expansion of macro 'STATE_MAP_ENTRY'
         STATE_MAP_ENTRY(ST_Idle)
         ^
C:\Users\xprk569\StateMachine\StateMachine.h:40:40: error: invalid use of member (did you forget the '&' ?)
     { reinterpret_cast<StateFunc>(entry) },
                                        ^
C:\Users\xprk569\StateMachine\Motor.h:30:9: note: in expansion of macro 'STATE_MAP_ENTRY'
         STATE_MAP_ENTRY(ST_Stop)
         ^
C:\Users\xprk569\StateMachine\StateMachine.h:40:40: error: invalid use of member (did you forget the '&' ?)
     { reinterpret_cast<StateFunc>(entry) },
                                        ^
C:\Users\xprk569\StateMachine\Motor.h:31:9: note: in expansion of macro 'STATE_MAP_ENTRY'
         STATE_MAP_ENTRY(ST_Start)
         ^
C:\Users\xprk569\StateMachine\StateMachine.h:40:40: error: invalid use of member (did you forget the '&' ?)
     { reinterpret_cast<StateFunc>(entry) },
                                        ^
C:\Users\xprk569\StateMachine\Motor.h:32:9: note: in expansion of macro 'STATE_MAP_ENTRY'
         STATE_MAP_ENTRY(ST_ChangeSpeed)
         ^
C:\Users\xprk569\StateMachine\StateMachine.h:43:39: error: invalid cast from type 'int' to type 'StateFunc {aka void (StateMachine::*)(EventData*)}'
     { reinterpret_cast<StateFunc>(NULL) }\
                                       ^
C:\Users\xprk569\StateMachine\Motor.h:33:5: note: in expansion of macro 'END_STATE_MAP'
     END_STATE_MAP
     ^
Process terminated with status 1 (0 minute(s), 0 second(s))
5 error(s), 0 warning(s) (0 minute(s), 0 second(s))

Can some please explain why is the macro written that way in Motor.h, why is it declared like that in StateMachine.h and why is the error being thrown ?

Thanks in Advance

It looks like this code depends on some nonstandard compiler extensions/errors.

To get it to compile (no idea if it will actually work) you need to replace the function names with full qualified member function pointers:

eg

BEGIN_STATE_MAP
    STATE_MAP_ENTRY(&Motor::ST_Idle)
    STATE_MAP_ENTRY(&Motor::ST_Stop)
    STATE_MAP_ENTRY(&Motor::ST_Start)
    STATE_MAP_ENTRY(&Motor::ST_ChangeSpeed)
END_STATE_MAP

After that, you need to figure out a way to overcome the non-conforming cast:

/tmp/gcc-explorer-compiler116314-75-1uiyu0/example.cpp: In member function 'virtual const StateStruct* Motor::GetStateMap()':
44 : error: invalid cast from type 'long int' to type 'StateFunc {aka void (StateMachine::*)(EventData*)}'
{ reinterpret_cast<StateFunc>(NULL) }\
^
83 : note: in expansion of macro 'END_STATE_MAP'

This cast is completely illegal. If I were you I would throw the code in the trash and rewrite - or use a proven state machine framework like boost meta state machine or boost statechart.

So you're quickly learning why Macros are no-nos in readable C++. If you get an error, you must expand the macro out to identify where the error is, also you cannot debug into them in most IDEs.

Anyway that said lets get to the expanding, their all the same error so we'll just look at the first one:

C:\\Users\\xprk569\\StateMachine\\Motor.h:29:9: note: in expansion of macro STATE_MAP_ENTRY
STATE_MAP_ENTRY(ST_Idle)
C:\\Users\\xprk569\\StateMachine\\StateMachine.h:40:40: error: invalid use of member (did you forget the & ?)
{ reinterpret_cast<StateFunc>(entry) },

So this is complaining about line 29: STATE_MAP_ENTRY(ST_Idle) So lets expand that:

{ reinterpret_cast<StateFunc>(entry) },

Obviously this is bad syntax all together outside the scoping of BEGIN_STATE_MAP and END_STATE_MAP , so in debugging many Macros you'd also have to look at the scoping macros... sometimes they may not be clearly named or delineated unfortunately, but let's finish defining the line we got the error on first. What is this StateFunc we're trying to cast to?

typedef void (StateMachine::*StateFunc)(EventData *);

It's a pointer to a member function which returns a void and accepts an EventData * . And alarm bells should be going off. You cannot cast to that! ST_Idle is of the format: void (StateMachine::*)() so you cannot cast to void (StateMachine::*StateFunc)(EventData *) . This is the same problem for all your functions passed into the macros none of them return a void and take an EventData* , so even if you fix the syntax, these reinterpret_cast s will always return a pointer to a method which is invalid to call, meaning this entire block of Macros is pointless at best and toxic at worst. In the current state you may just as well use none of these Macros or if you need to define the method just do:

BEGIN_STATE_MAP
END_STATE_MAP

But if you were going to change your method declarations to something more like:

void ST_Idle(EventData*);

Then you'd need to use this syntax:

STATE_MAP_ENTRY(&Motor::ST_Idle)

If you're not down with the method pointers they are quite complex. I've typed up a quick example here: http://ideone.com/nL0HnQ Feel free to comment with questions.

EDIT:

To expand the Macros here we'll get:

public: // BEGIN_STATE_MAP 
const StateStruct* GetStateMap() { // BEGIN_STATE_MAP 
    static const StateStruct StateMap[] = { // BEGIN_STATE_MAP 
    { reinterpret_cast<StateFunc>(ST_Idle) } // STATE_MAP_ENTRY(ST_Idle)
    { reinterpret_cast<StateFunc>(ST_Stop) } // STATE_MAP_ENTRY(ST_Stop)
    { reinterpret_cast<StateFunc>(ST_Start) } // STATE_MAP_ENTRY(ST_Start)
    { reinterpret_cast<StateFunc>(ST_ChangeSpeed) } // STATE_MAP_ENTRY(ST_ChangeSpeed)
    { reinterpret_cast<StateFunc>(NULL) } // END_STATE_MAP
    }; // END_STATE_MAP
    return &StateMap[0]; } // END_STATE_MAP

So this set of macros will:

  1. Set the scope to public
  2. Declare the method GetStateMap
  3. Declare StateMap statically local to GetStateMap , it will be an array of StateStruct s
  4. On the first call of the GetStateMap method StateMap will be initialized to contain method pointers to ST_Idle , ST_Stop , ST_Start , ST_ChangeSpeed , and NULL reinterpret_cast to StateFunc s
  5. Define GetStateMap to return the StateMap array

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