tags:

views:

117

answers:

3

I have a member function of an object that is typically used in an iterative manner but occasionally in a recursive manner. The function is basically following the path of a stream of water downhill, and under certain conditions the stream could split. In order to support the occasional recursion I have to push the state of the object onto the stack and pop it off afterwords. I'm currently doing this using local variables, but the code looks awful. I'd like to write PushState and PopState members but most of the implementations I've looked at aren't great. e.g. using a stack member in my object which ends up using the heap which is slow and leads to other issues. _alloca won't work as the stack frame is lost when the push function returns.

Are there any other reasonable generic ways of pushing and popping from the stack that i'm missing?

class CMyObject
{   
    enum Direction
    {
      left,right,branch,finished;
    }

    // state variables
    double m_XPos,m_YPos;

    void Navigate(double x,double y);
    Direction GoLeft();
    Direction GoLeft();
};

void    CMyObject::Navigate(double x,double y)
{
    m_XPos = x; m_YPos = y;

    Direction d = GoLeft(x,y);
    while(d != finished)
    {
     switch(d)
     {
      case left: d = GoLeft(); break;
      case right: d = GoRight(); break;
      case branch:
      {
       // push object state onto the stack
       double temp_x = m_XPos; double temp_y = m_YPos;
       Navigate(m_XPos,m_YPos);
       // pop object state from the stack
       m_XPos = temp_x; m_YPos = temp_x;
       GoRight();
      } break;
     }
    }
}
+2  A: 

I guess the obvious way would be to create a copy of your entire object and the do the recursive call on that. This way, each branch has it's own state, and you compiler does the stack management for you. Basically, a method has to be reentrant in order to be safely used in recursion. That is not the case here, since your methods rely on a persistant state. So you have to copy that state, and that means copying the object.

UPDATE:

Some more links about recursion and reentrancy:

Sadly, Donald Knuth has not yet finished the fourth part of his masterpiece, but when he does, read it, for it certainly clear that up.

Space_C0wb0y
You should read the article you pointed to, a reentrant function can be safely executed *concurrently* (as in from multiple threads), a function need not be reentrant to be recursive.
avakar
Read the section Examples, last sentence.
Space_C0wb0y
You probably mean the sentence that begins with the word "Typically".
avakar
Knew there had to be something blindingly obvious that I'd missed. Extra object for recursive branch it is. Thanks for that.
Shane MacLaughlin
A function could conceivably be reentrant with respect to recursion, but not reentrant with respect to threads. The POSIX spec uses the formula: "some function need not be reentrant. A function which is not required to be reentrant is not required to be thread-safe". So it seems odd to me to then say that reentrancy and thread-safety are the same thing.
Steve Jessop
I wish I had time to read the articles you posted (and study how POSIX uses the word, thank you Steve for a reference). Perhaps I got the meaning of "reentrant" wrong. However, intuitively, I believe that the OP's code is an example of code which is correct, recursive and not reentrant at the same time. (One more thing: I've always used the word reentrant as opposed to synchronized, i.e. a reentrant function can be executed concurrently without ever blocking -- a function can be thread-safe but not reentrant. But that's outside the scope of this discussion.)
avakar
Of course a function can be used recursively without being reentrant, but that may have undesired side-effects (as shown in the OPs code).
Space_C0wb0y
+1  A: 

I think you may need two objects - one that maintains position and any other state, and another which does the recursive navigation of an instance of the first object through the tree. In that way, the state of the navigated object will be restored when recursive calls in the navigator return.

anon
A: 

How about something like this?

struct Restore {
    Restore(double& value) : m_value(value), m_initial(value) {}
    ~Restore() {m_value = value;}
    double& m_value;
    double m_initial;
};

and then:

{ //scope
    Restore r1(m_XPos), r2(m_YPos);
    Navigate(m_XPos,m_YPos);
} // m_XPos and m_YPos automatically restored when r1 and r2 goes out of scope
Andreas Brinck
Shouldn't your destructor be `~Restore() {m_value = m_initial;}` ? It won't compile the way you wrote it (`~Restore() {m_value = value;}`) since `value` is not defined in the destructor.
Adisak