What is the best approach to this problem in C++?

Started by
15 comments, last by voguemaster 13 years, 5 months ago
I have a function in C++ that runs many sub-operations. Each sub-operation returns a boolean of whether it succeeds. It looks like this:

void Operation(){   return SubOperation1() && SubOperation2() && ... && SubOperationN();}


If any of the sub-operations fail along the way, the main operation terminates immediately. Simple.

Here's where it gets complicated. I want to be able to pause Operation() at any step along the way and resume it. I want to be able to run custom code between the SubOperations if necessary. Obviously, one way to do this is with a big switch statement that takes the current step as input and returns the next step.

int Operation(int i){   switch(i)   {       case 0: if (SubOperation1()) return 1; else return END;       case 1: if (SubOperation2()) return 2; else return END;       ...       case N: SubOperationN();       case END: return END;   }}


But it's not very clean. Adding new sub-operations requires a lot more work than before. Can you think of a better way to implement this?

[Edited by - gunning on November 11, 2010 2:37:24 PM]
....[size="1"]Brent Gunning
Advertisement
You'll need to retain some state, so wrapping the function into an object would make sense here. This object could have (or reference) a container of function pointers and a counter that indicates what operation it's at.

But, what exactly is the context you're working in?
Create-ivity - a game development blog Mouseover for more information.
Quote:Original post by Captain P
You'll need to retain some state, so wrapping the function into an object would make sense here. This object could have (or reference) a container of function pointers and a counter that indicates what operation it's at.

Yep a map of function pointers is one way to do this. However, it's still not as ideal as I would like. The suboperations may have different parameters so it's difficult to generalize. As a comparison, I saw a nice solution for this problem in C# that used a method that returns IEnumerable to yield the results of the sub-operations. Then the caller used the method in a foreach statement. After each yield, the caller has a chance to run its code. It looked similar to this:

class MyObject{   IEnumerable<bool> Operation()   {      yield return SubOperation1(...);      yield return SubOperation2(...);      ...      yield return SubOperationN(...);   }}// To use the operationforeach (bool success in myObject.Operation){   if (!success)      break;   // Custom code}


Here the caller doesn't have to know anything about the suboperations or the parameters they take.

Come to think of it, I don't need to pause and resume the operations. Just running code between each of the steps is good enough.

Quote:But, what exactly is the context you're working in?

Win32 program automation. I'm writing a program that automates actions in a GUI application and between each action I need to perform several tests. Also, I may want to sleep for a certain period of time to slow the automation code down for debugging purposes.



Edit: Fixed the C# example.

[Edited by - gunning on November 11, 2010 3:28:55 PM]
....[size="1"]Brent Gunning
This is what old-timers call a coroutine. There are a couple of implementations of coroutines available for C++, but I haven't tried them myself.

You can always launch the operation in its own thread and then communicate between threads. I imagine what those coroutines libraries provide is some sort of cooperative multithreading (see fiber).
To be pedantic, C# (via the .Net platform) provides generators, not full coroutines. (Yes, yes, you can mimic coroutines with generators, blah blah blah, but as I said I'm being pedantic [grin])


OP: I'd do some evil operator overloading magic for this one. Ideally the resultant code should look like this:

// Build the list of operations and fallbacksActionChain chain  = (PossiblyFailingOperation(param1, param2) | FailureResponse(param3)) >> (AnotherOperation(param4)) >> (SomeOtherOperation(param5, param6, param6) | MoreFailureResponse())  ;chain.InvokeChain();


This would build up a singly linked list whereby each operation points to its next link in the chain, as well as its failure mode. Each operation is derived from a base class, with two abstract virtual methods: Invoke() and GetNextLinkInChain(). Invoke() is obvious - it does the action based on the stored parameters. GetNextLinkInChain() uses the results of Invoke() to return one of three values: a pointer to the next operation to run, a pointer to the failure fallback operation to run, or NULL, signalling the end of execution.

It'll require a bit of boilerplate to get the base class into position, and a tiny bit of code overhead to wrap each operation into a structure rather than just a standalone function, but it should prove highly robust and compact.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

std::vector<std::pair<boost::function, boost::function>> functions_;


Would be one way to do it (in combination with boost::bind to tie arguments).
The first function in the pair would be your operation, the second function in the pair could be the code you want to run between operations.
Perhaps an interesting way would be to use lambdas (boost lambda or C++0x lambda) as the function you want to run between operations.
Anyway I'm just throwing stuff out here.
Quote:Original post by alvaro
This is what old-timers call a coroutine. There are a couple of implementations of coroutines available for C++, but I haven't tried them myself.

You can always launch the operation in its own thread and then communicate between threads. I imagine what those coroutines libraries provide is some sort of cooperative multithreading (see fiber).
Sweet. Coroutine was the term I was looking for. I just spent an hour on wikipedia and another hour reading the documentation for boost::coroutine. Yes the boost::coroutine lib (not officially a part of boost) does use Win32 fibers for the windows implementation. There was a forum post from last year that said it was unfinished and buggy, but I'm not convinced because there are so many examples so I'm going to give it a try and find out if it's true. Thanks.

Quote:To be pedantic, C# (via the .Net platform) provides generators, not full coroutines. (Yes, yes, you can mimic coroutines with generators, blah blah blah, but as I said I'm being pedantic )

Well actually ... don't you mean you can mimic semi-coroutines with generators? [smile]

(I don't claim to know what I'm talking about)

Quote:I'd do some evil operator overloading magic for this one. Ideally the resultant code should look like this:

Cool. This looks interesting. Where would you place the custom code to run between steps? Inside ActionChain?

Quote:std::vector<std::pair<boost::function, boost::function>> functions_;

I've never used the boost::function or std::pair templates before. When you bind the two functions together, does that guarantee that they always run together? And boost::function can generalize to any function with any parameters?

Thanks for the help so far.
....[size="1"]Brent Gunning
I'll reply later with the idea I had in mind (I'm just about to go out).
Quote:Original post by gunning
Quote:I'd do some evil operator overloading magic for this one. Ideally the resultant code should look like this:

Cool. This looks interesting. Where would you place the custom code to run between steps? Inside ActionChain?


I just realized I got your code backwards, sorry - you need the special code to execute after every success and abort the entire chain on failure, right?

In that case, tweak the code a bit:

// Build the list of operations and extra stepsActionChain chain  = (PossiblyFailingOperation(param1, param2) & ExtraCode(param3)) >> (AnotherOperation(param4)) >> (SomeOtherOperation(param5, param6, param6) & MoreExtraCode())  ;chain.InvokeChain();


The & operator simply adds an optional pointer to the extra Operation you want to run after each successful step; as your ActionChain walks through the sequence, it should be trivial to optionally invoke the extra code based on the return value of each Operation's Invoke() method.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Quote:Original post by gunning
I've never used the boost::function or std::pair templates before. When you bind the two functions together, does that guarantee that they always run together? And boost::function can generalize to any function with any parameters?

Yes, std::function (or boost::function if your implementation does not support std::function) supports an arbitrary number of parameters, and can be bound as closures using std::bind (or boost::bind, yadda yadda).

The idea of using pairs of functions is that if the .first succeeds, execute the .second, else abend.

Stephen M. Webb
Professional Free Software Developer

This topic is closed to new replies.

Advertisement