Sign in to follow this  
gunning

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

Recommended Posts

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]

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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 operation
foreach (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]

Share this post


Link to post
Share on other sites
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).

Share this post


Link to post
Share on other sites
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 fallbacks
ActionChain 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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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 steps
ActionChain 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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
Hi,

What is the real advantage of using boost::function or std::function for that matter compared to using normal interfaces and deriving objects, or just normal templates ?

consider, the case of interfaces:

class IOperationBase
{
public:
virtual bool operation()=0;
virtual bool PostOp()=0;
}

So each operation has its data in a derived class and just defines the two interface methods as a means to operate on the data.

You have:

std::vector<IOperationBase*> operations_;

and you iterate them, either normally or using std::for_each.

std::vector<IOperationBase*>::iterator opIter;
for(opIter = operations_.begin() ; opIter != operations_.end() ; opIter++)
{
IOperationBase* pOp = *opIter;
pOp->operation() && pOp->PostOp();
}

An example of a simple operation is:

class AddOp : public IOperationBase
{
public:
bool operations() { m_sum = m_x+m_y; };
bool PostOp() { cout << m_sum << endl; };

// data
int m_x;
int m_y;
int m_sum;
}


So its using inheritance but works nicely nonetheless. After all - you're already sacrificing something when you say you have a variable number of arguments and if this code is not super critical performance code (which is not to my understanding) then its a simple approach without too much hassle.

[Edited by - voguemaster on November 12, 2010 2:26:33 AM]

Share this post


Link to post
Share on other sites
Quote:
Original post by stevenmarky
std::vector<std::pair<boost::function, boost::function>> functions_;


Note that unless you are using C++0x, that ">>" is a MSVCism and is not valid C++.

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

Share this post


Link to post
Share on other sites
Quote:
Original post by voguemaster
Hi,

What is the real advantage of using boost::function or std::function for that matter compared to using normal interfaces and deriving objects, or just normal templates ?



Boilerplate. Look at the amount of code needed to run a single operation in your method, versus the amount needed using function (or, better, C++0x lambdas). That's a lot of cruft and a lot of potential for mistakes. It's also a lot of visual overhead that doesn't directly pertain to the important part of the code: actually doing the operations.

It also makes the setup of the master control code nasty; instead of just chaining together the ops you want, as in the minimal code posted by the OP or in my operator overload example, you have to jump through hoops and once again obscure the overall intention of the code. It's always best to represent your program in code that most closely visually resembles the intentions of the code itself.


(I will grant that my method involves some iffy boilerplate requirements as well; to be honest it mostly makes sense in a situation like constructing a lot of heavy unit tests where you want to spend more of your time in the high-level logic of selecting what operations to run and when, versus implementing the actual operations themselves. It's really only a win in that type of scenario.)

Share this post


Link to post
Share on other sites
I have a disgust for regular function pointers. I found boost::funtion to be elegant solution when I encountered it. Now I don't like it either single bit.

If you include it in headers you'll cripple the build time. If you use it with shared_ptr be careful it will keep reference count up. It's a pain in the ass to debug with.

I'm much more inclined to support voguemaster's suggestion.

I'm sure virtual funtions are faster and more readable solution.

Share this post


Link to post
Share on other sites
Until the proliferation of stupid little types built to adapt the functions you already have into this stupid functor arrangement crushes you under sheer weight.

Share this post


Link to post
Share on other sites
Quote:
Original post by Telastyn
Until the proliferation of stupid little types built to adapt the functions you already have into this stupid functor arrangement crushes you under sheer weight.

True, but then something has already gone wrong. Still don't see that the function pointer to massive amount of places is better alternative.

Ofcourse this issue with boost::funtion or virtual funtions is orthogonal, you can have functor that calls boost::funtion.

Share this post


Link to post
Share on other sites
Hi,

Telastyn, what you're saying sounds true at first glance but don't forget one thing - if you're using boost::function and you have to instantiate that templated construct with a different function for each type of operation you have, you're basically doing the same thing - creating mass amounts of types.

The difference as pointet out before is boilerplate. While true, I still prefer the virtual method for two reasons:

1. No need for outside library. Sometimes that wins hands down. You don't always want to add boost to your dependencies.

2. Sometimes even looking at template code or, god forbid, debugging build errors or just normally debugging template code can be a pain. So even with all its pluses, sometimes its still a pain :-)

And I always like to stick to the core concept of templates - if you have code that is common and you need to write it several times for different types then use templates. I don't see this here so I wouldn't have chosen a template-based solution here, even if on paper its a very nice one.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this