Search code examples
c++exceptionvisitor-pattern

How to stop a visitor?


A typical visitor pattern design looks like this:

template<class Visitor>
void processData(Visitor& visitor)
{
    // maybe in sequence
    visitor.process(...);
    visitor.process(...);
    ...

    // may have expensive computations
    doExpensiveComputations();

    // or in some loops
    for (...)
        visitor.process(...);

    // or in some recursive calls
    recursive(visitor);
}

How could the visitor return as soon as possible when it loses the interest of the rest data (e.g. the answer is found)?

Note that anything that needs to change the design is not acceptable, for example, I don't want to force Visitor::process return a value and check it every time.

What I really want to do is to force stack unwinding, I could use exception, but it has been told that using exception for control flow is anti-pattern.

I thought Boost.Coroutine may help, but it still uses exception for stack unwinding...

What I currently do is like below:

struct Visitor
{
    void process(T data)
    {
        if (stopped)
            return;
        ...
    }
    ...
};

But this would still go down the way of execution, as well as the expensive computations that we don't need.

Since there's no other portable way in c++ other than exceptions that can unwind the stack, I should just use exception for control flow here?


Solution

  • Note that anything that needs to change the design is not acceptable, for example, I don't want to force Visitor::process return a value and check it every time.

    By "design" in this case, I will assume you mean "the signature of Visitor::process".

    What I really want to do is to force stack unwinding, I could use exception, but it has been told that using exception for control flow is anti-pattern.

    I should just use exception for control flow here?

    Using an exception here is not necessarily an antipattern (as much as it is a "break execution" system - which can be an error or not).

    I encountered this scenario once ("use an exception to signal something else than error") and I used a thowable type (that was not inheriting from std::exception, directly or indirectly). My guideline for this is "if it inherits from std::exception, it is an error; otherwise, it is a signal that processing doesn't continue".

    Consider this implementation for your example:

    struct ProcessingInterrupted final {}; // <--- thin/empty implementation
                                           // not inheriting std exceptions
                                           // and not inheritable
    
    struct Visitor
    {
        void process(T data)
        {
            if(worldEnds)
                throw ProcessingInterrupted{};
            // ...
        }
        ...
    };
    
    template<class Visitor>
    bool process(Visitor& visitor)
    {
        try
        {
            processData(visitor); // taken from your example
            return true;
        }
        catch(const ProcessingInterrupted&)
        {
            return false;
        }
    }
    
    // client code
    Visitor v;
    /* auto success = */ process(v);
    

    With this, the ProcessingInterrupted type tells you exactly what is going on ("processing was interrupted") and the client code (for both the processData in your example and process in mine) looks minimalistic and has a clearly defined purpose.