C++ Boost

The Boost Statechart Library

Frequently Asked Questions (FAQs)


What's so cool about state-local storage?
How can I hide the inner workings of a state machine from its clients?
Is it possible to inherit from a given state machine and modify its layout in the subclass?
What about UML2.0 features?
Is Boost.Statechart suitable for embedded applications?
Is your library suitable for applications with hard real-time requirements?
With templated states I get an error that 'inner_context_type' is not defined. What's wrong?
My compiler reports an error in the library code. Is this a bug in Boost.Statechart?
How can I compile a state machine into a dynamic link library (DLL)?
Does Boost.Statechart support polymorphic events?
Why are exit-actions called in the wrong order when I use multiple inheritance?

What's so cool about state-local storage?

This is best explained with an example:

struct Active;
struct Stopped;
struct Running;
struct StopWatch : sc::state_machine< StopWatch, Active >
{
  // startTime_ remains uninitialized, because there is no reasonable default
  StopWatch() : elapsedTime_( 0.0 ) {}
  ~StopWatch()
  {
    terminate();
  }

  double ElapsedTime() const
  {
    // Ugly switch over the current state.
    if ( state_cast< const Stopped * >() != 0 )
    {
      return elapsedTime_;
    }
    else if ( state_cast< const Running * >() != 0 )
    {
      return elapsedTime_ + std::difftime( std::time( 0 ), startTime_ );
    }
    else // we're terminated
    {
      throw std::bad_cast();
    }
  }

  // elapsedTime_ is only meaningful when the machine is not terminated
  double elapsedTime_;
  // startTime_ is only meaningful when the machine is in Running
  std::time_t startTime_;
};

struct Active : sc::state< Active, StopWatch, Stopped >
{
  typedef sc::transition< EvReset, Active > reactions;

  Active( my_context ctx ) : my_base( ctx )
  {
    outermost_context().elapsedTime_ = 0.0;
  }
};

  struct Running : sc::state< Running, Active >
  {
    typedef sc::transition< EvStartStop, Stopped > reactions;

    Running( my_context ctx ) : my_base( ctx )
    {
      outermost_context().startTime_ = std::time( 0 );
    }

    ~Running()
    {
      outermost_context().elapsedTime_ +=
        std::difftime( std::time( 0 ), outermost_context().startTime_ );
    }
  };

  struct Stopped : sc::simple_state< Stopped, Active >
  {
    typedef sc::transition< EvStartStop, Running > reactions;
  };

This StopWatch does not make any use of state-local storage while implementing the same behavior as the tutorial StopWatch. Even though this code is probably easier to read for the untrained eye, it does have a few problems that are absent in the original:

Both points are not much of a problem in a small example like this, which can easily be implemented in a single translation unit by a single programmer. However, they quickly become a major problem for a big complex machine spread over multiple translation units, which are possibly even maintained by different programmers.

How can I hide the inner workings of a state machine from its clients?

To see why and how this is possible it is important to recall the following facts:

The class template member function state_machine<>::initiate() creates an object of the initial state. So, the definition of this state must be known before the compiler reaches the point where initiate() is called. To be able to hide the initial state of a state machine in a .cpp file we must therefore no longer let clients call initiate(). Instead, we do so in the .cpp file, at a point where the full definition of the initial state is known.

Example:

StopWatch.hpp:

// define events ...

struct Active; // the only visible forward
struct StopWatch : sc::state_machine< StopWatch, Active >
{
  StopWatch();
};

StopWatch.cpp:

struct Stopped;
struct Active : sc::simple_state< Active, StopWatch, Stopped >
{
  typedef sc::transition< EvReset, Active > reactions;
};

  struct Running : sc::simple_state< Running, Active >
  {
    typedef sc::transition< EvStartStop, Stopped > reactions;
  };

  struct Stopped : sc::simple_state< Stopped, Active >
  {
    typedef sc::transition< EvStartStop, Running > reactions;
  };

StopWatch::StopWatch()
{
  // For example, we might want to ensure that the state
  // machine is already started after construction.
  // Alternatively, we could add our own initiate() function
  // to StopWatch and call the base class initiate() in the
  // implementation.
  initiate();
}

Is it possible to inherit from a given state machine and modify its layout in the subclass?

Yes, but contrary to what some FSM code generators allow, Boost.Statechart machines can do so only in a way that was foreseen by the designer of the base state machine:

struct EvStart : sc::event< EvStart > {};

struct Idle;
struct PumpBase : sc::state_machine< PumpBase, Idle >
{
  virtual sc::result react(
    Idle & idle, const EvStart & ) const;
};

struct Idle : sc::simple_state< Idle, PumpBase >
{
  typedef sc::custom_reaction< EvStart > reactions;

  sc::result react( const EvStart & evt )
  {
    return context< PumpBase >().react( *this, evt );
  }
};

struct Running : sc::simple_state< Running, PumpBase > {};

sc::result PumpBase::react(
  Idle & idle, const EvStart & ) const
{
  return idle.transit< Running >();
}


struct MyRunning : sc::simple_state< MyRunning, PumpBase > {};

struct MyPump : PumpBase
{
  virtual sc::result react(
    Idle & idle, const EvStart & ) const
  {
    return idle.transit< MyRunning >();
  }
};

What about UML 2.0 features?

The library was designed before 2.0 came along. Therefore, if not explicitly noted otherwise, the library implements the behavior mandated by the UML1.5 standard. Here's an incomplete list of differences between the 2.0 semantics & Boost.Statechart semantics:

Is Boost.Statechart suitable for embedded applications?

It depends. As explained under Speed versus scalability tradeoffs on the Performance page, the virtually limitless scalability offered by this library does have its price. Especially small and simple FSMs can easily be implemented so that they consume fewer cycles and less memory and occupy less code space in the executable. Here are some obviously very rough estimates:

As mentioned above, these are very rough estimates derived from the use of the library on a desktop PC, so they should only be used to decide whether there is a point in making your own performance tests on your target platform.

Is your library suitable for applications with hard real-time requirements?

Yes. Out of the box, the only operations taking potentially non-deterministic time that the library performs are calls to std::allocator<> member functions and dynamic_casts. std::allocator<> member function calls can be avoided by passing a custom allocator to event<>, state_machine<>, asynchronous_state_machine<>, fifo_scheduler<> and fifo_worker<>. dynamic_casts can be avoided by not calling the state_cast<> member functions of state_machine<>, simple_state<> and state<> but using the deterministic variant state_downcast<> instead.

With templated states I get an error that 'inner_context_type' is not defined. What's wrong?

The following code generates such an error:

#include <boost/statechart/state_machine.hpp>
#include <boost/statechart/simple_state.hpp>

namespace sc = boost::statechart;

template< typename X > struct A;
struct Machine : sc::state_machine< Machine, A< int > > {};

template< typename X > struct B;
template< typename X >
struct A : sc::simple_state< A< X >, Machine, B< X > > {};

  template< typename X >
  struct B : sc::simple_state< B< X >, A< X > > {};

int main()
{
  Machine machine;
  machine.initiate();
  return 0;
}

If the templates A and B are replaced with normal types, the above code compiles without errors. This is rooted in the fact that C++ treats forward-declared templates differently than forward-declared types. Namely, the compiler tries to access member typedefs of B< X > at a point where the template has not yet been defined. Luckily, this can easily be avoided by putting all inner initial state arguments in an mpl::list<>, as follows:

struct A : sc::simple_state<
  A< X >, Machine, mpl::list< B< X > > > {};

See this post for technical details.

My compiler reports an error in the library code. Is this a bug in Boost.Statechart?

Probably not. There are several possible reasons for such compile-time errors:

  1. Your compiler is too buggy to compile the library, see here for information on the status of your compiler. If you absolutely must use such a compiler for your project, I'm afraid Boost.Statechart is not for you.
  2. The error is reported on a line similar to the following:
    BOOST_STATIC_ASSERT( ( mpl::less<
      orthogonal_position,
      typename context_type::no_of_orthogonal_regions >::value ) );
    
    Most probably, there is an error in your code. The library has many such compile-time assertions to ensure that invalid state machines cannot be compiled (for an idea what kinds of errors are reported at compile time, see the compile-fail tests). Above each of these assertions there is a comment explaining the problem. On almost all current compilers an error in template code is accompanied by the current "instantiation stack". Very much like the call stack you see in the debugger, this "instantiation stack" allows you to trace the error back through instantiations of library code until you hit the line of your code that causes the problem. As an example, here's the MSVC7.1 error message for the code in InconsistentHistoryTest1.cpp:
    ...\boost\statechart\shallow_history.hpp(34) : error C2027: use of undefined type 'boost::STATIC_ASSERTION_FAILURE<x>'
      with
      [
        x=false
      ]
      ...\boost\statechart\shallow_history.hpp(34) : see reference to class template instantiation 'boost::STATIC_ASSERTION_FAILURE<x>' being compiled
      with
      [
        x=false
      ]
      ...\boost\statechart\simple_state.hpp(861) : see reference to class template instantiation 'boost::statechart::shallow_history<DefaultState>' being compiled
      with
      [
        DefaultState=B
      ]
      ...\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
      with
      [
        MostDerived=A,
        Context=InconsistentHistoryTest,
        InnerInitial=boost::mpl::list<boost::statechart::shallow_history<B>>,
        InnerList=boost::statechart::simple_state<A,InconsistentHistoryTest,boost::mpl::list<boost::statechart::shallow_history<B>>>::inner_initial_list
      ]
      ...\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
      with
      [
        MostDerived=A,
        Context=InconsistentHistoryTest,
        InnerInitial=boost::mpl::list<boost::statechart::shallow_history<B>>
      ]
      ...\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 &)'
      with
      [
        MostDerived=A,
        Context=InconsistentHistoryTest,
        InnerInitial=boost::mpl::list<boost::statechart::shallow_history<B>>
      ]
      ...\libs\statechart\test\InconsistentHistoryTest1.cpp(29) : see reference to class template instantiation 'boost::statechart::simple_state<MostDerived,Context,InnerInitial>' being compiled
      with
      [
        MostDerived=A,
        Context=InconsistentHistoryTest,
        InnerInitial=boost::mpl::list<boost::statechart::shallow_history<B>>
      ]
    
    Depending on the IDE you use, it is possible that you need to switch to another window to see this full error message (e.g. for Visual Studio 2003, you need to switch to the Output window). Starting at the top and going down the list of instantiations you see that each of them is accompanied by a file name and a line number. Ignoring all files belonging to the library, we find the culprit close to the bottom in file InconsistentHistoryTest1.cpp on line 29.
  3. The error is reported on a line nowhere near a BOOST_STATIC_ASSERT. Use the technique described under point 2 to see what line of your code causes the problem. If your code is correct then you've found a bug in either the compiler or Boost.Statechart. Please send me a small but complete program showing the problem. Thank you!

How can I compile a state machine into a dynamic link library (DLL)?

Invisible to the user, the library uses static data members to implement its own speed-optimized RTTI-mechanism for event<> and simple_state<> subtypes. Whenever such a subtype is defined in a header file and then included in multiple TUs, the linker later needs to eliminate the duplicate definitions of static data members. This usually works flawlessly as long as all these TUs are statically linked into the same binary. It is a lot more complex when DLLs are involved. The TuTest*.?pp files illustrate this:

Without any precautions (e.g. __declspec(dllexport) on MSVC compatible compilers), on most platforms both binaries (exe & dll) now contain their own instance of the static data member. Since the RTTI mechanism assumes that there is exactly one object of that member at runtime, the mechanism fails spectacularly when the process running the exe also loads the dll. Different platforms deal differently with this problem:

Does Boost.Statechart support polymorphic events?

No. Although events can be derived from each other to write common code only once, reactions can only be defined for most-derived events.

Example:

template< class MostDerived >
struct EvButtonPressed : sc::event< MostDerived >
{
  // common code
};

struct EvPlayButtonPressed :
  EvButtonPressed< EvPlayButtonPressed > {};
struct EvStopButtonPressed :
  EvButtonPressed< EvStopButtonPressed > {};
struct EvForwardButtonPressed :
  EvButtonPressed< EvForwardButtonPressed > {};

/* ... */

// We want to turn the player on, no matter what button we
// press in the Off state. Although we can write the reaction
// code only once, we must mention all most-derived events in
// the reaction list.
struct Off : sc::simple_state< Off, Mp3Player >
{
  typedef mpl::list<
    sc::custom_reaction< EvPlayButtonPressed >,
    sc::custom_reaction< EvStopButtonPressed >,
    sc::custom_reaction< EvForwardButtonPressed >
  > reactions;

  template< class MostDerived >
  sc::result react( const EvButtonPressed< MostDerived > & )
  {
    // ...
  }
};

Why are exit-actions called in the wrong order when I use multiple inheritance?

Update: The implementation has changed considerably in this area. It is still possible to get this behavior under rare circumstances (when an action propagates an exception in a state machine with orthogonal regions and if the statechart layout satisfies certain conditions), but it can no longer be demonstrated with the example program below. However, the described workaround is still valid and ensures that this behavior will never show up.

They definitely aren't for the simple_state<> and state<> subtypes, but the destructors of additional bases might be called in construction order (rather than the reverse construction order):

#include <boost/statechart/state_machine.hpp>
#include <boost/statechart/simple_state.hpp>

namespace sc = boost::statechart;

class EntryExitDisplayer
{
  protected:
    EntryExitDisplayer( const char * pName ) :
      pName_( pName )
    {
      std::cout << pName_ << " entered\n";
    }

    ~EntryExitDisplayer()
    {
      std::cout << pName_ << " exited\n";
    }

  private:
    const char * const pName_;
};

struct Outer;
struct Machine : sc::state_machine< Machine, Outer > {};
struct Inner;
struct Outer : EntryExitDisplayer, sc::simple_state<
  Outer, Machine, Inner >
{
  Outer() : EntryExitDisplayer( "Outer" ) {}
};

struct Inner : EntryExitDisplayer,
  sc::simple_state< Inner, Outer >
{
  Inner() : EntryExitDisplayer( "Inner" ) {}
};

int main()
{
  Machine myMachine;
  myMachine.initiate();
  return 0;
}

This program will produce the following output:

Outer entered
Inner entered
Outer exited
Inner exited

That is, the EntryExitDisplayer base class portion of Outer is destructed before the one of Inner although Inner::~Inner() is called before Outer::~Outer(). This somewhat counter-intuitive behavior is caused by the following facts:

So, when the Outer destructor is called the call stack looks as follows:

Outer::~Outer()
simple_state< Inner, ... >::~simple_state()
Inner::~Inner()

Note that Inner::~Inner() did not yet have a chance to destroy its EntryExitDisplayer base class portion, as it first has to call the destructor of the second base class. Now Outer::~Outer() will first destruct its simple_state< Outer, ... > base class portion and then do the same with its EntryExitDisplayer base class portion. The stack then unwinds back to Inner::~Inner(), which can then finally finish by calling EntryExitDisplayer::~EntryExitDisplayer().

Luckily, there is an easy work-around: Always let simple_state<> and state<> be the first base class of a state. This ensures that destructors of additional bases are called before recursion employed by state base destructors can alter the order of destruction.


Valid HTML 4.01 Transitional

Revised 03 December, 2006

Copyright © 2003-2006 Andreas Huber Dönni

Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)