

Example: add % button to QCalc
|
![]() |
class Calc1 : public QHsm
{
public:
static Calc1* instance(); // public singleton accessor method
protected:
Calc1() : QHsm((QPseudoState)initial) {} // protected constructor
protected: // state handlers
void initial(QEvent const *e);
virtual QSTATE calc(QEvent const *e);
virtual QSTATE ready(QEvent const *e);
virtual QSTATE result(QEvent const *e);
virtual QSTATE begin(QEvent const *e);
virtual QSTATE negated1(QEvent const *e);
virtual QSTATE operand1(QEvent const *e);
virtual QSTATE zero1(QEvent const *e);
virtual QSTATE int1(QEvent const *e);
virtual QSTATE frac1(QEvent const *e);
virtual QSTATE opEntered(QEvent const *e);
virtual QSTATE negated2(QEvent const *e);
virtual QSTATE operand2(QEvent const *e);
virtual QSTATE zero2(QEvent const *e);
virtual QSTATE int2(QEvent const *e);
virtual QSTATE frac2(QEvent const *e);
virtual QSTATE final(QEvent const *e);
protected: // other methods
virtual void clear();
virtual void insert(int keyId);
virtual void negate();
virtual void eval();
void dispState(char const *s);
protected: // data
// windows stuff plus...
double myOperand1;
double myOperand2;
int myOperator;
};
QSTATE Calc1::calc(QEvent const *e)
{
switch (e->sig)
{
case Q_ENTRY_SIG:
dispState("calc");
return 0;
case Q_INIT_SIG:
clear();
Q_INIT(&Calc1::ready);
return 0;
case IDC_C:
clear();
Q_TRAN(&Calc1::calc);
return 0;
case TERMINATE:
Q_TRAN(&Calc1::final);
return 0;
}
if (e->sig >= Q_USER_SIG)
{
isHandled = FALSE;
}
return (QSTATE)&Calc1::top; // unchanged
}
QSTATE Calc1::zero2(QEvent const *e)
{
switch (e->sig)
{
case Q_ENTRY_SIG:
dispState("zero2");
return 0;
case IDC_1_9:
insert(((CalcEvt *)e)->keyId);
Q_TRAN(&Calc1::int2);
return 0;
case IDC_POINT:
insert(((CalcEvt *)e)->keyId);
Q_TRAN(&Calc1::frac2);
return 0;
}
return (QSTATE)&Calc1::operand2; // unchanged
}
QSTATE Calc1::int2(QEvent const *e)
{
switch (e->sig)
{
case Q_ENTRY_SIG:
dispState("int2");
return 0;
case IDC_0:
case IDC_1_9:
insert(((CalcEvt *)e)->keyId);
return 0;
case IDC_POINT:
insert(((CalcEvt *)e)->keyId);
Q_TRAN(&Calc1::frac2);
return 0;
}
return (QSTATE)&Calc1::operand2; // unchanged
}
QSTATE Calc1::frac2(QEvent const *e)
{
switch (e->sig)
{
case Q_ENTRY_SIG:
dispState("frac2");
return 0;
case IDC_0:
case IDC_1_9:
insert(((CalcEvt *)e)->keyId);
return 0;
}
return (QSTATE)&Calc1::operand2; // unchanged
}
...
QSTATE Calc1::operand2(QEvent const *e)
{
switch (e->sig)
{
case Q_ENTRY_SIG:
dispState("operand2");
return 0;
case IDC_CE:
clear();
Q_TRAN(&Calc1::opEntered);
return 0;
case IDC_OPER:
sscanf(myDisplay, "%lf", &myOperand2);
eval();
sscanf(myDisplay, "%lf", &myOperand1);
myOperator = ((CalcEvt *)e)->keyId;
Q_TRAN(&Calc1::opEntered);
return 0;
case IDC_EQUALS:
sscanf(myDisplay, "%lf", &myOperand2);
Q_TRAN(&Calc1::result);
return 0;
}
return (QSTATE)&Calc1::calc; // unchanged
}
...

class Calc2 : public Calc1
{
protected:
virtual QSTATE operand2(QEvent const *e);
}; // that's all! only handlers (methods) requiring modification
QSTATE Calc2::operand2(QEvent const *e)
{
switch (e->sig)
{
case IDC_PERCENT:
sscanf(myDisplay, "%lf", &myOperand2);
switch (myOperator)
{
case IDC_PLUS:
myOperand2 = 1.0 + myOperand2/100.0; // x*(1 + y%)
myOperator = IDC_MULT;
break;
case IDC_MINUS:
myOperand2 = 1.0 - myOperand2/100.0; // x*(1 - y%)
myOperator = IDC_MULT;
break;
case IDC_MULT: // intentionally fall thru...
case IDC_DIVIDE:
myOperand2 /= 100.0; // x*y%, x/y%
break;
default:
ASSERT(0);
}
Q_TRAN(&Calc2::result);
return 0; // event handled
}
return Calc1::operand2(e); // let Calc1 handle other events
/* NOTE: This returns the result of a function call to the base class
handler, not directly a pointer-to-member as expected for a
state handler. The base class handler first handles the
event, then returns the parent handler. Thus the parent
state is unchanged and determined by the base class
handler. */
}

The mechanism employed in this model inheritance is a polymorphic indirection not seen in typical OOP. The derived class method (handler) returns the address of a base class handler whiich in turn is called. This base class handler then calls another base class method which is overridden in the derived class. It is not entirely clear whether this call accesses the derived version of this overridden method.
Of course, you would expect that if things work as polymorphism policy requires, the call would differentiate at runtime, just as ordinary pointers to objects invoke the appropriate version at runtime. In fact this is accomplished. The optimal mechanism accomplishing this polymorphic indirection is the thunk, explained somewhat in the textbook and also in more detail in our course Generic Programming. The slide illustrates a simple demonstration program. The full program is quoted below, along with screen output. The code can be cut/paste into a file and tested on your own compiler.
/* p2mdemo.cpp
demonstration of polymorphic runtime binding
applied to pointer-to-virtual-member functions
*/
#include <iostream>
class Base
{
public:
typedef void (Base::*Handler)();
Base();
virtual ~Base();
Handler f();
virtual void g();
};
class Derived : public Base
{
public:
Derived();
virtual ~Derived();
virtual void g();
};
Base:: Base() { std::cout << "Base::Constructor\n"; }
Base:: ~Base() { std::cout << "Base::Destructor\n"; }
Derived:: Derived() { std::cout << "Derived::Constructor\n"; }
Derived:: ~Derived() { std::cout << "Derived::Destructor\n"; }
Base::Handler Base:: f() { return &Base::g; }
void Base:: g() { std::cout << " Base::g()\n"; }
void Derived:: g() { std::cout << " Derived::g()\n"; }
Base B;
Derived D;
Base::Handler h = B.f();
int main()
{
(B.*h)();
(D.*h)();
}
/* Output:
Base::Constructor
Base::Constructor
Derived::Constructor
Base::g()
Derived::g()
Derived::Destructor
Base::Destructor
Base::Destructor
*/


mySource = Q_STATE_CAST((this->*mySource)(e))This a method invokation that casts up and back down the inheritance hierarchy:
typedef QPseudoState (QHsm::*QState)(QEvent const*); // type QState
QState mySource; // a pointer-to-member of base class
mySource = Q_STATE_CAST(...) // upcast of returned state handler
this ->* // through a pointer-to-base-class-member
mySource // de-ref a pointer-to-derived-class-member
(e) // evaluation of derived class member
Invoking a pointer-to-derived-class-member through a
pointer-to-base-class-member has unpredictable outcome under
multiple inheritance.
