From: Robert Bell (belvis_at_[hidden])
Date: 2004-05-27 17:03:43
Andreas Huber wrote:
> Robert Bell <belvis <at> imageworks.com> writes:
>>>However, no matter from what angle I start to think this through I always
>>>end up with the same bad feeling. If we introduce entry()/exit() for the
>>>reasons you give (more flexibility for optimization?)
>>More flexibility to change the implementation, for any reason (not
> Why else would you want to change the implementation (while leaving the
> interface as is)?
I don't know, I can't predict the future. Maybe you'll come up with an
implementation strategy that you think is superior.
Is it true that the only reason you've ever had for changing an
implementation is optimization? That's certainly not been true for me.
>>By using constructors and destructors for
>>entry and exit, you're telling users that fsm guarantess that States
>>will be constructed and destroyed at specific times, which means that
>>for all time fsm must provide those guarantees.
> Yes, what's so bad about that? We'd have to guarantee the same with entry()
> and exit().
It strikes me that making that guarantee with entry() and exit() may be
easier to maintain if the implementation changes.
>>>this essentially means
>>>to tell the users that they must not under any circumstances rely on when
>>>exactly constructors and destructors of state objects are called.
>>Keep in mind that it's entirely possible that I'm missign something.
>>That said, they can't rely on when exactly constructors and destructors
>>are called today.
> Yes they can. It is clearly defined when state ctors/dtors are called.
I guess I was interpreting the word "exactly" a little differently.
Saying "the constructor is called when the state is entered" means that
it won't be called until the right sequence of events occurs, so I can't
know "exactly" when the constructor will be called.
>>>worse, they must not even rely on how many times state ctors/dtors are
>>They can't rely on the number of times they're called now.
> Again, yes they can. The docs clearly define how often they are called.
Same interpretation: I don't know how many times the state will be
entered, so I don't know how many times the constructor will be run.
>> > Today they might be called exactly as often as entry()/exit() but
>>>tomorrow they might be called only exactly once.
>>None of these issues having to do with when and how many times
>>constructors and destructors are called sound bad to me; why would
Regardless of what was meant by "exactly" (it's clear I misinterpreted),
I thought this was the more important point, so I'll ask again. Why
would anyone care when and how many times the constructors and
destructors are called?
>> > This means that most
>>>non-POD data members of states need to be created on the heap, as you
>>>otherwise wouldn't have a chance to create them inside entry() and destroy
>>>them inside exit(). And believe me, you definitely need to do that when you
>>>design non-trivial FSMs. Therefore, to me entry()/exit() means falling at
>>>least halfway back into medieval times where you always had to explictly
>>>call constructors and destructors yourself (Turbo Pascal).
>>I don't quite see it this way myself, but in any event, this is a
>>concern for the implementer of fsm, not a user of fsm.
> No, users are directly affected. Assume for a moment that we have entry()/exit
> () and that you want to create a non-pod object of type A in the entry action
> and destroy it in the exit action. For the reasons given before you cannot do
> this in the ctor/dtor. So, your state object cannot have a data member of type
> A, instead you need to either have A * (or the smart equivalent) as member. In
> entry(), you then allocate a new A object on the heap and set the data member
> pointer to that object. In exit() you need to delete the object and set the
> pointer back to 0.
I see what you're saying; I thought you meant something else by
"explictly call constructors and destructors yourself".
Even if it's common to want to create a non-POD in an entry action and
then destroy it in the corresponding exit action, I don't see a problem
with doing the allocation in entry() and deallocation in exit(). But I'm
not even convinced that this usage is common anyway; do you have reason
to think so?
>>>- Nobody has so far presented hard technical facts that support the view
>>>that mapping entry/exit to ctor/dtor is wrong. To the contrary, I think I
>>>have almost proved in my discussion with Dave that exit actions must not
>>>fail and that you *can* recover from failing entry actions.
>>I'm not so sure there _are_ hard technical facts; there are just two
>>different interface choices, each with different tradeoffs.
> So what exactly are the tradeoffs then, if it isn't performance?
I've tried to give a sense of a maintenance tradeoff, but apparently
I've failed. Here are more off the top of my head:
-- demands that the state be created when it's entered and destroyed
when it's exited
-- no possibility for State member data to persist after exit unless the
user somehow provides for it
-- simple implementation of the "allocate a non-POD at entry/deallocate
at exit" use case
-- only one state (plus parents) exists at a time; siblings cannot
communicate with each other except through means such as global flags
-- only demands that a State be created once
-- allows State data to persist after exit
-- slightly more complicated implementation of the "allocate a non-POD
at entry/deallocate at exit" use case
-- allows siblings to exist at the same time
>>I'm sure you've thought this out more than I have. I'm just coming from
>>a gut-level reaction I have to linking entry/exit to
>>construction/destruction. It just feels weird. For example, if entry and
>>exit actions are things a state "does", then I'd expect them to be
>>overridable; constructors and destructors can't be overridden.
> How exactly would you want to override entry and exit actions? I mean, I
> personally haven't ever seen a framework allowing this, much less UML.
I'm not sure what your question means. If entry and exit actions are
implemented as member functions called entry() and exit(), how else
would you use them except by making them virtual and overriding?
>>(In the past, when I've implemented state machines I've made Action a
>>separate class; that way, Actions are attached to States, which makes it
>>easier to reuse Actions in different States.)
> While I do sometimes reuse transition actions, I have never felt the urge to
> do so for entry and exit actions. If you need to do that, no problem: Just
> call a function from ctor/dtor
If I remember correctly, the reason this design evolved was because of a
need to reuse entry/exit actions (although I don't remember exactly what
the actions were). As I went forward, it made more and more sense to
separate States and Actions. States represent the state the machine can
be in; Actions do things. Having this separation made it easy to do
things like combine Actions in interesting ways, such as defining a
composite Action that executed a list of other Actions.
One example of a reuse of an entry Action was for debugging purposes. An
Action that prints the ID of the State could be attached as an entry
Action for each State to generate a trace of a machine's behavior.
Another reason for this design was that it occured to me that by having
actions be member functions of State, it requires the user to derive new
State classes to get different actions. One implication this had that I
didn't like was that since the actual classes of the States in a machine
would be dynamic, constructing a state machine was more complicated. I
wanted to be able to do things like read a state machine description
from an XML file and build it; if there's just one State class that's
used, this is pretty easy. Otherwise some kind of factory is needed.
Since the only reason I ever derived from State was for actions,
separating actions into their own classes meant that State wouldn't be
needed as a base class.
I'm not saying that this is the way boost.fsm should go, just trying to
give a little more context about why I did it the way I did. Again, it's
about tradeoffs; my goals were obviously different from those of boost.fsm.
>>I don't quite understand the objection
>>to entry() and exit() member functions, but then again I don't have to
>>understand it for fsm to proceed.
> The objection is that it violates KISS. If there are hard *technical*
> arguments in favor of entry()/exit() then I'll happily add them. I hope we
> agree that so far no such arguments have been presented.
KISS is subjective; what's simple to one may not be simple to another.
I'm not convinced that constructor/destructor is simpler than
entry()/exit(), but I'm not convinced that entry()/exit() is simpler
than constructor/destructor either (entry()/exit() "feels" simpler to
me, but I can't make a much stronger statement than that).
We can agree that there have been no hard *technical* reasons in favor
of entry() and exit(). I'm certain that we disagree there have been no
hard *technical* reasons in favor of constructor/destructor.
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk