|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
<html>
|
|
Packit |
58578d |
<head>
|
|
Packit |
58578d |
<meta http-equiv="Content-Language" content="en-us">
|
|
Packit |
58578d |
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
|
|
Packit |
58578d |
<meta name="GENERATOR" content="Microsoft FrontPage 6.0">
|
|
Packit |
58578d |
<meta name="ProgId" content="FrontPage.Editor.Document">
|
|
Packit |
58578d |
<link rel="stylesheet" type="text/css" href="../../../boost.css">
|
|
Packit |
58578d |
|
|
Packit |
58578d |
<title>The Boost Statechart Library - FAQ</title>
|
|
Packit |
58578d |
</head>
|
|
Packit |
58578d |
|
|
Packit |
58578d |
<body link="#0000FF" vlink="#800080">
|
|
Packit |
58578d |
|
|
Packit |
58578d |
"header">
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
"../../../boost.png" border="0" width="277" height="86">
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
The Boost Statechart Library
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Frequently Asked Questions (FAQs)
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
What's so cool about state-local
|
|
Packit |
58578d |
storage?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
How can I hide the inner workings of a
|
|
Packit |
58578d |
state machine from its clients?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Is it possible to inherit from a given
|
|
Packit |
58578d |
state machine and modify its layout in the subclass?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
What about UML2.0 features?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Why do I get an assert when I
|
|
Packit |
58578d |
access the state machine from a state destructor?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Is Boost.Statechart suitable for
|
|
Packit |
58578d |
embedded applications?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Is your library suitable for applications
|
|
Packit |
58578d |
with hard real-time requirements?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
With templated states I get an error that
|
|
Packit |
58578d |
'inner_context_type' is not defined. What's wrong?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
My compiler reports an error in the library
|
|
Packit |
58578d |
code. Is this a bug in Boost.Statechart?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Is it possible to disable history for a
|
|
Packit |
58578d |
state at runtime?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
How can I compile a state machine into a dynamic link
|
|
Packit |
58578d |
library (DLL)?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Does Boost.Statechart support
|
|
Packit |
58578d |
polymorphic events?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Why are exit-actions called in the
|
|
Packit |
58578d |
wrong order when I use multiple inheritance?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
state-local storage?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
This is best explained with an example:
|
|
Packit |
58578d |
|
|
Packit |
58578d |
struct Active;
|
|
Packit |
58578d |
struct Stopped;
|
|
Packit |
58578d |
struct Running;
|
|
Packit |
58578d |
struct StopWatch : sc::state_machine< StopWatch, Active >
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
// startTime_ remains uninitialized, because there is no reasonable default
|
|
Packit |
58578d |
StopWatch() : elapsedTime_( 0.0 ) {}
|
|
Packit |
58578d |
~StopWatch()
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
terminate();
|
|
Packit |
58578d |
}
|
|
Packit |
58578d |
|
|
Packit |
58578d |
double ElapsedTime() const
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
// Ugly switch over the current state.
|
|
Packit |
58578d |
if ( state_cast< const Stopped * >() != 0 )
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
return elapsedTime_;
|
|
Packit |
58578d |
}
|
|
Packit |
58578d |
else if ( state_cast< const Running * >() != 0 )
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
return elapsedTime_ + std::difftime( std::time( 0 ), startTime_ );
|
|
Packit |
58578d |
}
|
|
Packit |
58578d |
else // we're terminated
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
throw std::bad_cast();
|
|
Packit |
58578d |
}
|
|
Packit |
58578d |
}
|
|
Packit |
58578d |
|
|
Packit |
58578d |
// elapsedTime_ is only meaningful when the machine is not terminated
|
|
Packit |
58578d |
double elapsedTime_;
|
|
Packit |
58578d |
// startTime_ is only meaningful when the machine is in Running
|
|
Packit |
58578d |
std::time_t startTime_;
|
|
Packit |
58578d |
};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
struct Active : sc::state< Active, StopWatch, Stopped >
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
typedef sc::transition< EvReset, Active > reactions;
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Active( my_context ctx ) : my_base( ctx )
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
outermost_context().elapsedTime_ = 0.0;
|
|
Packit |
58578d |
}
|
|
Packit |
58578d |
};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
struct Running : sc::state< Running, Active >
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
typedef sc::transition< EvStartStop, Stopped > reactions;
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Running( my_context ctx ) : my_base( ctx )
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
outermost_context().startTime_ = std::time( 0 );
|
|
Packit |
58578d |
}
|
|
Packit |
58578d |
|
|
Packit |
58578d |
~Running()
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
outermost_context().elapsedTime_ +=
|
|
Packit |
58578d |
std::difftime( std::time( 0 ), outermost_context().startTime_ );
|
|
Packit |
58578d |
}
|
|
Packit |
58578d |
};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
struct Stopped : sc::simple_state< Stopped, Active >
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
typedef sc::transition< EvStartStop, Running > reactions;
|
|
Packit |
58578d |
};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
This StopWatch does not make any use of state-local storage while
|
|
Packit |
58578d |
implementing the same behavior as the
|
|
Packit |
58578d |
"tutorial.html#BasicTopicsAStopWatch">tutorial StopWatch. Even though
|
|
Packit |
58578d |
this code is probably easier to read for the untrained eye, it does have a
|
|
Packit |
58578d |
few problems that are absent in the original:
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
This StopWatch class has data members that have a meaningful value
|
|
Packit |
58578d |
only if the state machine happens to be in a certain state. That is, the
|
|
Packit |
58578d |
lifetimes of these variables are not identical with the one of the
|
|
Packit |
58578d |
StopWatch object containing them. Since the lifetimes are managed by the
|
|
Packit |
58578d |
entry and exit actions of states, we need to use an ugly switch over the
|
|
Packit |
58578d |
current state (see StopWatch::ElapsedTime() ) if we want to
|
|
Packit |
58578d |
access them from a context where the current state is unclear. This
|
|
Packit |
58578d |
essentially duplicates some of the state logic of the FSM. Therefore,
|
|
Packit |
58578d |
whenever we need to change the layout of the state machine we will likely
|
|
Packit |
58578d |
also need to change the ugly switch. Even worse, if we forget to change
|
|
Packit |
58578d |
the switch, the code will probably still compile and maybe even silently
|
|
Packit |
58578d |
do the wrong thing. Note that this is impossible with the version in the
|
|
Packit |
58578d |
tutorial, which will at least throw an exception and often just refuse to
|
|
Packit |
58578d |
compile. Moreover, for the tutorial StopWatch there's a much higher
|
|
Packit |
58578d |
chance that a programmer will get a change correct the first time since
|
|
Packit |
58578d |
the code that calculates the elapsed time is located close to the code
|
|
Packit |
58578d |
that updates the variables
|
|
Packit |
58578d |
|
|
Packit |
58578d |
We need to change the StopWatch class whenever we want to introduce a
|
|
Packit |
58578d |
new variable or change the type of an already existing variable. That is,
|
|
Packit |
58578d |
many changes in the FSM will likely lead to a change in the StopWatch
|
|
Packit |
58578d |
class. In all FSMs that do not employ state-local storage, the
|
|
Packit |
58578d |
state_machine<> subtype will therefore be a change
|
|
Packit |
58578d |
hotspot, which is a pretty sure indicator for a bad design
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Both points are not much of a problem in a small example like this,
|
|
Packit |
58578d |
which can easily be implemented in a single translation unit by a single
|
|
Packit |
58578d |
programmer. However, they quickly become a major problem for a big complex
|
|
Packit |
58578d |
machine spread over multiple translation units, which are possibly even
|
|
Packit |
58578d |
maintained by different programmers.
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
inner workings of a state machine from its clients?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
To see why and how this is possible it is important to recall the
|
|
Packit |
58578d |
following facts:
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Member functions of a C++ class template are instantiated at the
|
|
Packit |
58578d |
point where they're actually called. If the function is never called, it
|
|
Packit |
58578d |
will not be instantiated and not a single assembly instruction will ever
|
|
Packit |
58578d |
be generated
|
|
Packit |
58578d |
|
|
Packit |
58578d |
The InitialState template parameter of
|
|
Packit |
58578d |
sc::state_machine can be an incomplete type (i.e. forward
|
|
Packit |
58578d |
declared)
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
The class template member function
|
|
Packit |
58578d |
state_machine<>::initiate() creates an object of the
|
|
Packit |
58578d |
initial state. So, the definition of this state must be known before the
|
|
Packit |
58578d |
compiler reaches the point where initiate() is called. To be
|
|
Packit |
58578d |
able to hide the initial state of a state machine in a .cpp file we must
|
|
Packit |
58578d |
therefore no longer let clients call initiate() . Instead, we
|
|
Packit |
58578d |
do so in the .cpp file, at a point where the full definition of the initial
|
|
Packit |
58578d |
state is known.
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Example:
|
|
Packit |
58578d |
|
|
Packit |
58578d |
StopWatch.hpp:
|
|
Packit |
58578d |
|
|
Packit |
58578d |
// define events ...
|
|
Packit |
58578d |
|
|
Packit |
58578d |
struct Active; // the only visible forward
|
|
Packit |
58578d |
struct StopWatch : sc::state_machine< StopWatch, Active >
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
StopWatch();
|
|
Packit |
58578d |
};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
StopWatch.cpp:
|
|
Packit |
58578d |
|
|
Packit |
58578d |
struct Stopped;
|
|
Packit |
58578d |
struct Active : sc::simple_state< Active, StopWatch, Stopped >
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
typedef sc::transition< EvReset, Active > reactions;
|
|
Packit |
58578d |
};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
struct Running : sc::simple_state< Running, Active >
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
typedef sc::transition< EvStartStop, Stopped > reactions;
|
|
Packit |
58578d |
};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
struct Stopped : sc::simple_state< Stopped, Active >
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
typedef sc::transition< EvStartStop, Running > reactions;
|
|
Packit |
58578d |
};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
StopWatch::StopWatch()
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
// For example, we might want to ensure that the state
|
|
Packit |
58578d |
// machine is already started after construction.
|
|
Packit |
58578d |
// Alternatively, we could add our own initiate() function
|
|
Packit |
58578d |
// to StopWatch and call the base class initiate() in the
|
|
Packit |
58578d |
// implementation.
|
|
Packit |
58578d |
initiate();
|
|
Packit |
58578d |
}
|
|
Packit |
58578d |
|
|
Packit |
58578d |
The PingPong example demonstrates how the inner workings of an
|
|
Packit |
58578d |
asynchronous_state_machine<> subclass can be hidden.
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
inherit from a given state machine and modify its layout in the
|
|
Packit |
58578d |
subclass?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Yes, but contrary to what some FSM code generators allow,
|
|
Packit |
58578d |
Boost.Statechart machines can do so only in a way that was foreseen by the
|
|
Packit |
58578d |
designer of the base state machine:
|
|
Packit |
58578d |
|
|
Packit |
58578d |
struct EvStart : sc::event< EvStart > {};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
struct Idle;
|
|
Packit |
58578d |
struct PumpBase : sc::state_machine< PumpBase, Idle >
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
virtual sc::result react(
|
|
Packit |
58578d |
Idle & idle, const EvStart & ) const;
|
|
Packit |
58578d |
};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
struct Idle : sc::simple_state< Idle, PumpBase >
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
typedef sc::custom_reaction< EvStart > reactions;
|
|
Packit |
58578d |
|
|
Packit |
58578d |
sc::result react( const EvStart & evt )
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
return context< PumpBase >().react( *this, evt );
|
|
Packit |
58578d |
}
|
|
Packit |
58578d |
};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
struct Running : sc::simple_state< Running, PumpBase > {};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
sc::result PumpBase::react(
|
|
Packit |
58578d |
Idle & idle, const EvStart & ) const
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
return idle.transit< Running >();
|
|
Packit |
58578d |
}
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
struct MyRunning : sc::simple_state< MyRunning, PumpBase > {};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
struct MyPump : PumpBase
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
virtual sc::result react(
|
|
Packit |
58578d |
Idle & idle, const EvStart & ) const
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
return idle.transit< MyRunning >();
|
|
Packit |
58578d |
}
|
|
Packit |
58578d |
};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
The library was designed before 2.0 came along. Therefore, if not
|
|
Packit |
58578d |
explicitly noted otherwise, the library implements the behavior mandated by
|
|
Packit |
58578d |
the UML1.5 standard. Here's an incomplete list of differences between the
|
|
Packit |
58578d |
2.0 semantics & Boost.Statechart semantics:
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
All transitions are always external. Local transitions are not
|
|
Packit |
58578d |
supported at all. Unfortunately, the UML2.0 specifications are not
|
|
Packit |
58578d |
entirely clear how local transitions are supposed to work, see
|
|
Packit |
58578d |
"http://thread.gmane.org/gmane.comp.lib.boost.user/18641">here for
|
|
Packit |
58578d |
more information
|
|
Packit |
58578d |
|
|
Packit |
58578d |
There is no direct support for the UML2.0 elements entry point and
|
|
Packit |
58578d |
exit point. However, both can easily be simulated, the former with a
|
|
Packit |
58578d |
typedef and the latter with a state that is a template (with the
|
|
Packit |
58578d |
transition destination as a template parameter)
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
get an assert when I access the state machine from a state destructor?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
When compiled with NDEBUG undefined, running the following
|
|
Packit |
58578d |
program results in a failed assert:
|
|
Packit |
58578d |
#include <boost/statechart/state_machine.hpp>
|
|
Packit |
58578d |
#include <boost/statechart/simple_state.hpp>
|
|
Packit |
58578d |
#include <iostream>
|
|
Packit |
58578d |
|
|
Packit |
58578d |
struct Initial;
|
|
Packit |
58578d |
struct Machine : boost::statechart::state_machine< Machine, Initial >
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
Machine() { someMember_ = 42; }
|
|
Packit |
58578d |
int someMember_;
|
|
Packit |
58578d |
};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
struct Initial : boost::statechart::simple_state< Initial, Machine >
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
~Initial() { std::cout << outermost_context().someMember_; }
|
|
Packit |
58578d |
};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
int main()
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
Machine().initiate();
|
|
Packit |
58578d |
return 0;
|
|
Packit |
58578d |
}
|
|
Packit |
58578d |
The problem arises because state_machine<>::~state_machine
|
|
Packit |
58578d |
inevitably destructs all remaining active states. At this time,
|
|
Packit |
58578d |
Machine::~Machine has already been run, making it illegal to
|
|
Packit |
58578d |
access any of the Machine members. This problem can be avoided
|
|
Packit |
58578d |
by defining the following destructor:
|
|
Packit |
58578d |
~Machine() { terminate(); }
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Boost.Statechart suitable for embedded applications?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
It depends. As explained under
|
|
Packit |
58578d |
"performance.html#SpeedVersusScalabilityTradeoffs">Speed versus scalability
|
|
Packit |
58578d |
tradeoffs on the Performance page, the virtually limitless scalability
|
|
Packit |
58578d |
offered by this library does have its price. Especially small and simple
|
|
Packit |
58578d |
FSMs can easily be implemented so that they consume fewer cycles and less
|
|
Packit |
58578d |
memory and occupy less code space in the executable. Here are some
|
|
Packit |
58578d |
obviously very rough estimates:
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
For a state machine with at most one simultaneously active state
|
|
Packit |
58578d |
(that is, the machine is flat and does not have orthogonal regions) with
|
|
Packit |
58578d |
trivial actions, customized memory management and compiled with a good
|
|
Packit |
58578d |
optimizing compiler, a Pentium 4 class CPU should not spend more than
|
|
Packit |
58578d |
1000 cycles inside state_machine<>::process_event() .
|
|
Packit |
58578d |
This worst-case time to process one event scales more or less linearly
|
|
Packit |
58578d |
with the number of simultaneously active states for more complex state
|
|
Packit |
58578d |
machines, with the typical average being much lower than that. So, a
|
|
Packit |
58578d |
fairly complex machine with at most 10 simultaneously active states
|
|
Packit |
58578d |
running on a 100MHz CPU should be able to process more than 10'000 events
|
|
Packit |
58578d |
per second
|
|
Packit |
58578d |
|
|
Packit |
58578d |
A single state machine object uses typically less than 1KB of memory,
|
|
Packit |
58578d |
even if it implements a very complex machine
|
|
Packit |
58578d |
|
|
Packit |
58578d |
For code size, it is difficult to give a concrete guideline but tests
|
|
Packit |
58578d |
with the BitMachine example suggest that code size scales more or less
|
|
Packit |
58578d |
linearly with the number of states (transitions seem to have only little
|
|
Packit |
58578d |
impact). When compiled with MSVC7.1 on Windows, 32 states and 224
|
|
Packit |
58578d |
transitions seem to fit in ~108KB executable code (with all optimizations
|
|
Packit |
58578d |
turned on).
|
|
Packit |
58578d |
Moreover, the library can be compiled with C++ RTTI and exception
|
|
Packit |
58578d |
handling turned off, resulting in significant savings on most
|
|
Packit |
58578d |
platforms
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
As mentioned above, these are very rough estimates derived from the use
|
|
Packit |
58578d |
of the library on a desktop PC, so they should only be used to decide
|
|
Packit |
58578d |
whether there is a point in making your own performance tests on your
|
|
Packit |
58578d |
target platform.
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
applications with hard real-time requirements?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Yes. Out of the box, the only operations taking potentially
|
|
Packit |
58578d |
non-deterministic time that the library performs are calls to
|
|
Packit |
58578d |
std::allocator<> member functions and
|
|
Packit |
58578d |
dynamic_cast s. std::allocator<> member
|
|
Packit |
58578d |
function calls can be avoided by passing a custom allocator to
|
|
Packit |
58578d |
event<> , state_machine<> ,
|
|
Packit |
58578d |
asynchronous_state_machine<> ,
|
|
Packit |
58578d |
fifo_scheduler<> and fifo_worker<> .
|
|
Packit |
58578d |
dynamic_cast s can be avoided by not calling the
|
|
Packit |
58578d |
state_cast<> member functions of
|
|
Packit |
58578d |
state_machine<> , simple_state<> and
|
|
Packit |
58578d |
state<> but using the deterministic variant
|
|
Packit |
58578d |
state_downcast<> instead.
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
get an error that 'inner_context_type' is not defined. What's
|
|
Packit |
58578d |
wrong?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
The following code generates such an error:
|
|
Packit |
58578d |
|
|
Packit |
58578d |
#include <boost/statechart/state_machine.hpp>
|
|
Packit |
58578d |
#include <boost/statechart/simple_state.hpp>
|
|
Packit |
58578d |
|
|
Packit |
58578d |
namespace sc = boost::statechart;
|
|
Packit |
58578d |
|
|
Packit |
58578d |
template< typename X > struct A;
|
|
Packit |
58578d |
struct Machine : sc::state_machine< Machine, A< int > > {};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
template< typename X > struct B;
|
|
Packit |
58578d |
template< typename X >
|
|
Packit |
58578d |
struct A : sc::simple_state< A< X >, Machine, B< X > > {};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
template< typename X >
|
|
Packit |
58578d |
struct B : sc::simple_state< B< X >, A< X > > {};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
int main()
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
Machine machine;
|
|
Packit |
58578d |
machine.initiate();
|
|
Packit |
58578d |
return 0;
|
|
Packit |
58578d |
}
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
If the templates A and B are replaced with
|
|
Packit |
58578d |
normal types, the above code compiles without errors. This is rooted in the
|
|
Packit |
58578d |
fact that C++ treats forward-declared templates differently than
|
|
Packit |
58578d |
forward-declared types. Namely, the compiler tries to access member
|
|
Packit |
58578d |
typedefs of B< X > at a point where the template has not
|
|
Packit |
58578d |
yet been defined. Luckily, this can easily be avoided by putting all inner
|
|
Packit |
58578d |
initial state arguments in an mpl::list<> , as
|
|
Packit |
58578d |
follows:
|
|
Packit |
58578d |
|
|
Packit |
58578d |
struct A : sc::simple_state<
|
|
Packit |
58578d |
A< X >, Machine, mpl::list< B< X > > > {};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
See
|
|
Packit |
58578d |
"http://article.gmane.org/gmane.comp.lib.boost.devel/128741">this post
|
|
Packit |
58578d |
for technical details.
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
in the library code. Is this a bug in Boost.Statechart?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Probably not. There are several possible reasons for such compile-time
|
|
Packit |
58578d |
errors:
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Your compiler is too buggy to compile the library, see
|
|
Packit |
58578d |
"index.html#SupportedPlatforms">here for information on the status of
|
|
Packit |
58578d |
your compiler. If you absolutely must use such a compiler for your
|
|
Packit |
58578d |
project, I'm afraid Boost.Statechart is not for you.
|
|
Packit |
58578d |
|
|
Packit |
58578d |
The error is reported on a line similar to the following:
|
|
Packit |
58578d |
|
|
Packit |
58578d |
BOOST_STATIC_ASSERT( ( mpl::less<
|
|
Packit |
58578d |
orthogonal_position,
|
|
Packit |
58578d |
typename context_type::no_of_orthogonal_regions >::value ) );
|
|
Packit |
58578d |
Most probably, there is an error in your code. The library has many
|
|
Packit |
58578d |
such compile-time assertions to ensure that invalid state machines cannot be
|
|
Packit |
58578d |
compiled (for an idea what kinds of errors are reported at compile time, see
|
|
Packit |
58578d |
the compile-fail tests). Above each of these assertions there is a comment
|
|
Packit |
58578d |
explaining the problem. On almost all current compilers an error in template
|
|
Packit |
58578d |
code is accompanied by the current "instantiation stack". Very much like the
|
|
Packit |
58578d |
call stack you see in the debugger, this "instantiation stack" allows you to
|
|
Packit |
58578d |
trace the error back through instantiations of library code until you hit the
|
|
Packit |
58578d |
line of your code that causes the problem. As an example, here's the MSVC7.1
|
|
Packit |
58578d |
error message for the code in InconsistentHistoryTest1.cpp:
|
|
Packit |
58578d |
|
|
Packit |
58578d |
...\boost\statechart\shallow_history.hpp(34) : error C2027: use of undefined type 'boost::STATIC_ASSERTION_FAILURE<x>'
|
|
Packit |
58578d |
with
|
|
Packit |
58578d |
[
|
|
Packit |
58578d |
x=false
|
|
Packit |
58578d |
]
|
|
Packit |
58578d |
...\boost\statechart\shallow_history.hpp(34) : see reference to class template instantiation 'boost::STATIC_ASSERTION_FAILURE<x>' being compiled
|
|
Packit |
58578d |
with
|
|
Packit |
58578d |
[
|
|
Packit |
58578d |
x=false
|
|
Packit |
58578d |
]
|
|
Packit |
58578d |
...\boost\statechart\simple_state.hpp(861) : see reference to class template instantiation 'boost::statechart::shallow_history<DefaultState>' being compiled
|
|
Packit |
58578d |
with
|
|
Packit |
58578d |
[
|
|
Packit |
58578d |
DefaultState=B
|
|
Packit |
58578d |
]
|
|
Packit |
58578d |
...\boost\statechart\simple_state.hpp(599) : see reference to function template instantiation 'void boost::statechart::simple_state<MostDerived,Context,InnerInitial>::deep_construct_inner_impl_non_empty::deep_construct_inner_impl<InnerList>(const boost::statechart::simple_state<MostDerived,Context,InnerInitial>::inner_context_ptr_type &,boost::statechart::simple_state<MostDerived,Context,InnerInitial>::outermost_context_base_type &)' being compiled
|
|
Packit |
58578d |
with
|
|
Packit |
58578d |
[
|
|
Packit |
58578d |
MostDerived=A,
|
|
Packit |
58578d |
Context=InconsistentHistoryTest,
|
|
Packit |
58578d |
InnerInitial=boost::mpl::list<boost::statechart::shallow_history<B>>,
|
|
Packit |
58578d |
InnerList=boost::statechart::simple_state<A,InconsistentHistoryTest,boost::mpl::list<boost::statechart::shallow_history<B>>>::inner_initial_list
|
|
Packit |
58578d |
]
|
|
Packit |
58578d |
...\boost\statechart\simple_state.hpp(567) : see reference to function template instantiation 'void boost::statechart::simple_state<MostDerived,Context,InnerInitial>::deep_construct_inner<boost::statechart::simple_state<MostDerived,Context,InnerInitial>::inner_initial_list>(const boost::statechart::simple_state<MostDerived,Context,InnerInitial>::inner_context_ptr_type &,boost::statechart::simple_state<MostDerived,Context,InnerInitial>::outermost_context_base_type &)' being compiled
|
|
Packit |
58578d |
with
|
|
Packit |
58578d |
[
|
|
Packit |
58578d |
MostDerived=A,
|
|
Packit |
58578d |
Context=InconsistentHistoryTest,
|
|
Packit |
58578d |
InnerInitial=boost::mpl::list<boost::statechart::shallow_history<B>>
|
|
Packit |
58578d |
]
|
|
Packit |
58578d |
...\boost\statechart\simple_state.hpp(563) : while compiling class-template member function 'void boost::statechart::simple_state<MostDerived,Context,InnerInitial>::deep_construct(const boost::statechart::simple_state<MostDerived,Context,InnerInitial>::context_ptr_type & ,boost::statechart::simple_state<MostDerived,Context,InnerInitial>::outermost_context_base_type &)'
|
|
Packit |
58578d |
with
|
|
Packit |
58578d |
[
|
|
Packit |
58578d |
MostDerived=A,
|
|
Packit |
58578d |
Context=InconsistentHistoryTest,
|
|
Packit |
58578d |
InnerInitial=boost::mpl::list<boost::statechart::shallow_history<B>>
|
|
Packit |
58578d |
]
|
|
Packit |
58578d |
...\libs\statechart\test\InconsistentHistoryTest1.cpp(29) : see reference to class template instantiation 'boost::statechart::simple_state<MostDerived,Context,InnerInitial>' being compiled
|
|
Packit |
58578d |
with
|
|
Packit |
58578d |
[
|
|
Packit |
58578d |
MostDerived=A,
|
|
Packit |
58578d |
Context=InconsistentHistoryTest,
|
|
Packit |
58578d |
InnerInitial=boost::mpl::list<boost::statechart::shallow_history<B>>
|
|
Packit |
58578d |
]
|
|
Packit |
58578d |
Depending on the IDE you use, it is possible that you need to switch to
|
|
Packit |
58578d |
another window to see this full error message (e.g. for Visual Studio 2003,
|
|
Packit |
58578d |
you need to switch to the Output window). Starting at the top and going down
|
|
Packit |
58578d |
the list of instantiations you see that each of them is accompanied by a file
|
|
Packit |
58578d |
name and a line number. Ignoring all files belonging to the library, we find
|
|
Packit |
58578d |
the culprit close to the bottom in file InconsistentHistoryTest1.cpp on line
|
|
Packit |
58578d |
29.
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
The error is reported on a line nowhere near a BOOST_STATIC_ASSERT.
|
|
Packit |
58578d |
Use the technique described under point 2 to see what line of your code
|
|
Packit |
58578d |
causes the problem. If your code is correct then you've found a bug in
|
|
Packit |
58578d |
either the compiler or Boost.Statechart. Please
|
|
Packit |
58578d |
"contact.html">send me a small but complete program showing the
|
|
Packit |
58578d |
problem. Thank you!
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
history for a state at runtime?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Yes, see
|
|
Packit |
58578d |
"reference.html#clear_shallow_history">simple_state::clear_shallow_history()
|
|
Packit |
58578d |
and
|
|
Packit |
58578d |
"reference.html#clear_deep_history">simple_state::clear_deep_history().
|
|
Packit |
58578d |
Calling these functions is often preferable to introducting additional
|
|
Packit |
58578d |
normal transitions when ...
|
|
Packit |
58578d |
|
|
Packit |
58578d |
a state with history is the target of many transitions,
|
|
Packit |
58578d |
and/or
|
|
Packit |
58578d |
the decision to ignore history is made in a different place than
|
|
Packit |
58578d |
the transition to a state with history
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
link library (DLL)?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Invisible to the user, the library uses static data members to implement
|
|
Packit |
58578d |
its own speed-optimized RTTI-mechanism for event<> and
|
|
Packit |
58578d |
simple_state<> subtypes. Whenever such a subtype is
|
|
Packit |
58578d |
defined in a header file and then included in multiple TUs, the linker
|
|
Packit |
58578d |
later needs to eliminate the duplicate definitions of static data members.
|
|
Packit |
58578d |
This usually works flawlessly as long as all these TUs are
|
|
Packit |
58578d |
statically linked into the same binary. It is a lot more complex
|
|
Packit |
58578d |
when DLLs are involved. The TuTest*.?pp files illustrate this:
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
TuTest.hpp: Instantiates a class
|
|
Packit |
58578d |
template containing a static data member
|
|
Packit |
58578d |
|
|
Packit |
58578d |
TuTest.cpp: Includes TuTest.hpp and
|
|
Packit |
58578d |
is compiled into a DLL
|
|
Packit |
58578d |
|
|
Packit |
58578d |
TuTestMain.cpp: Includes
|
|
Packit |
58578d |
TuTest.hpp and is compiled into an executable
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Without any precautions (e.g. __declspec(dllexport) on MSVC
|
|
Packit |
58578d |
compatible compilers), on most platforms both binaries (exe & dll) now
|
|
Packit |
58578d |
contain their own instance of the static data member. Since the RTTI
|
|
Packit |
58578d |
mechanism assumes that there is exactly one object of that member at
|
|
Packit |
58578d |
runtime, the mechanism fails spectacularly when the process running the exe
|
|
Packit |
58578d |
also loads the dll. Different platforms deal differently with this
|
|
Packit |
58578d |
problem:
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
On some platforms (e.g. MinGW) there simply doesn't seem to be a way
|
|
Packit |
58578d |
to enforce that such a member only exists once at runtime. Therefore, the
|
|
Packit |
58578d |
internal RTTI mechanism cannot be used reliably in conjunction with DLLs.
|
|
Packit |
58578d |
Disabling it by defining
|
|
Packit |
58578d |
"configuration.html#ApplicationDefinedMacros">BOOST_STATECHART_USE_NATIVE_RTTI
|
|
Packit |
58578d |
in all TUs will usually work around the problem
|
|
Packit |
58578d |
|
|
Packit |
58578d |
MSVC-compatible compilers support __declspec(dllimport)
|
|
Packit |
58578d |
and __declspec(dllexport) , which allow to define exactly
|
|
Packit |
58578d |
what needs to be loaded from a DLL (see TuTest for an example how to do
|
|
Packit |
58578d |
this). Therefore, the internal RTTI mechanism can be used but care must
|
|
Packit |
58578d |
be taken to correctly export and import all event<>
|
|
Packit |
58578d |
and simple_state<> subtypes defined in headers that
|
|
Packit |
58578d |
are compiled into more than one binary. Alternatively, of course
|
|
Packit |
58578d |
"configuration.html#ApplicationDefinedMacros">BOOST_STATECHART_USE_NATIVE_RTTI
|
|
Packit |
58578d |
can also be used to save the work of importing and exporting
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Boost.Statechart support polymorphic events?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
No. Although events can be derived from each other to write common code
|
|
Packit |
58578d |
only once, reactions can only be
|
|
Packit |
58578d |
defined for most-derived events.
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Example:
|
|
Packit |
58578d |
|
|
Packit |
58578d |
template< class MostDerived >
|
|
Packit |
58578d |
struct EvButtonPressed : sc::event< MostDerived >
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
// common code
|
|
Packit |
58578d |
};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
struct EvPlayButtonPressed :
|
|
Packit |
58578d |
EvButtonPressed< EvPlayButtonPressed > {};
|
|
Packit |
58578d |
struct EvStopButtonPressed :
|
|
Packit |
58578d |
EvButtonPressed< EvStopButtonPressed > {};
|
|
Packit |
58578d |
struct EvForwardButtonPressed :
|
|
Packit |
58578d |
EvButtonPressed< EvForwardButtonPressed > {};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
/* ... */
|
|
Packit |
58578d |
|
|
Packit |
58578d |
// We want to turn the player on, no matter what button we
|
|
Packit |
58578d |
// press in the Off state. Although we can write the reaction
|
|
Packit |
58578d |
// code only once, we must mention all most-derived events in
|
|
Packit |
58578d |
// the reaction list.
|
|
Packit |
58578d |
struct Off : sc::simple_state< Off, Mp3Player >
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
typedef mpl::list<
|
|
Packit |
58578d |
sc::custom_reaction< EvPlayButtonPressed >,
|
|
Packit |
58578d |
sc::custom_reaction< EvStopButtonPressed >,
|
|
Packit |
58578d |
sc::custom_reaction< EvForwardButtonPressed >
|
|
Packit |
58578d |
> reactions;
|
|
Packit |
58578d |
|
|
Packit |
58578d |
template< class MostDerived >
|
|
Packit |
58578d |
sc::result react( const EvButtonPressed< MostDerived > & )
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
// ...
|
|
Packit |
58578d |
}
|
|
Packit |
58578d |
};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
exit-actions called in the wrong order when I use multiple
|
|
Packit |
58578d |
inheritance?
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Update: The implementation has changed considerably in this area.
|
|
Packit |
58578d |
It is still possible to get this behavior under rare circumstances (when an
|
|
Packit |
58578d |
action propagates an exception in a state machine with orthogonal regions
|
|
Packit |
58578d |
and if the statechart layout satisfies certain conditions), but it
|
|
Packit |
58578d |
can no longer be demonstrated with the example program below. However, the
|
|
Packit |
58578d |
described workaround is still valid and ensures that this behavior will
|
|
Packit |
58578d |
never show up.
|
|
Packit |
58578d |
|
|
Packit |
58578d |
They definitely aren't for the simple_state<> and
|
|
Packit |
58578d |
state<> subtypes, but the destructors of additional
|
|
Packit |
58578d |
bases might be called in construction order (rather than the reverse
|
|
Packit |
58578d |
construction order):
|
|
Packit |
58578d |
|
|
Packit |
58578d |
#include <boost/statechart/state_machine.hpp>
|
|
Packit |
58578d |
#include <boost/statechart/simple_state.hpp>
|
|
Packit |
58578d |
|
|
Packit |
58578d |
namespace sc = boost::statechart;
|
|
Packit |
58578d |
|
|
Packit |
58578d |
class EntryExitDisplayer
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
protected:
|
|
Packit |
58578d |
EntryExitDisplayer( const char * pName ) :
|
|
Packit |
58578d |
pName_( pName )
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
std::cout << pName_ << " entered\n";
|
|
Packit |
58578d |
}
|
|
Packit |
58578d |
|
|
Packit |
58578d |
~EntryExitDisplayer()
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
std::cout << pName_ << " exited\n";
|
|
Packit |
58578d |
}
|
|
Packit |
58578d |
|
|
Packit |
58578d |
private:
|
|
Packit |
58578d |
const char * const pName_;
|
|
Packit |
58578d |
};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
struct Outer;
|
|
Packit |
58578d |
struct Machine : sc::state_machine< Machine, Outer > {};
|
|
Packit |
58578d |
struct Inner;
|
|
Packit |
58578d |
struct Outer : EntryExitDisplayer, sc::simple_state<
|
|
Packit |
58578d |
Outer, Machine, Inner >
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
Outer() : EntryExitDisplayer( "Outer" ) {}
|
|
Packit |
58578d |
};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
struct Inner : EntryExitDisplayer,
|
|
Packit |
58578d |
sc::simple_state< Inner, Outer >
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
Inner() : EntryExitDisplayer( "Inner" ) {}
|
|
Packit |
58578d |
};
|
|
Packit |
58578d |
|
|
Packit |
58578d |
int main()
|
|
Packit |
58578d |
{
|
|
Packit |
58578d |
Machine myMachine;
|
|
Packit |
58578d |
myMachine.initiate();
|
|
Packit |
58578d |
return 0;
|
|
Packit |
58578d |
}
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
This program will produce the following output:
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Outer entered
|
|
Packit |
58578d |
Inner entered
|
|
Packit |
58578d |
Outer exited
|
|
Packit |
58578d |
Inner exited
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
That is, the EntryExitDisplayer base class portion
|
|
Packit |
58578d |
of Outer is destructed before the one of Inner
|
|
Packit |
58578d |
although Inner::~Inner() is called before
|
|
Packit |
58578d |
Outer::~Outer() . This somewhat counter-intuitive behavior is
|
|
Packit |
58578d |
caused by the following facts:
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
The simple_state<> base class portion of
|
|
Packit |
58578d |
Inner is responsible to destruct Outer
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Destructors of base class portions are called in the reverse order of
|
|
Packit |
58578d |
construction
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
So, when the Outer destructor is called the call stack
|
|
Packit |
58578d |
looks as follows:
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Outer::~Outer()
|
|
Packit |
58578d |
simple_state< Inner, ... >::~simple_state()
|
|
Packit |
58578d |
Inner::~Inner()
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Note that Inner::~Inner() did not yet have a chance to
|
|
Packit |
58578d |
destroy its EntryExitDisplayer base class portion, as it first
|
|
Packit |
58578d |
has to call the destructor of the second base class. Now
|
|
Packit |
58578d |
Outer::~Outer() will first destruct its simple_state<
|
|
Packit |
58578d |
Outer, ... > base class portion and then do the same with its
|
|
Packit |
58578d |
EntryExitDisplayer base class portion. The stack then unwinds
|
|
Packit |
58578d |
back to Inner::~Inner() , which can then finally finish by
|
|
Packit |
58578d |
calling EntryExitDisplayer::~EntryExitDisplayer() .
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Luckily, there is an easy work-around: Always let
|
|
Packit |
58578d |
simple_state<> and state<> be the
|
|
Packit |
58578d |
first base class of a state. This ensures that destructors of additional
|
|
Packit |
58578d |
bases are called before recursion employed by state base destructors can
|
|
Packit |
58578d |
alter the order of destruction.
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
|
|
Packit |
58578d |
"../../../doc/images/valid-html401.png" alt="Valid HTML 4.01 Transitional"
|
|
Packit |
58578d |
height="31" width="88">
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Revised 05 January, 2008
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Copyright © 2003-2008 Andreas Huber
|
|
Packit |
58578d |
Dönni
|
|
Packit |
58578d |
|
|
Packit |
58578d |
Distributed under the Boost Software License, Version 1.0. (See
|
|
Packit |
58578d |
accompanying file LICENSE_1_0.txt or
|
|
Packit |
58578d |
copy at
|
|
Packit |
58578d |
"http://www.boost.org/LICENSE_1_0.txt">http://www.boost.org/LICENSE_1_0.txt)
|
|
Packit |
58578d |
</body>
|
|
Packit |
58578d |
</html>
|