jasonm76
jasonm76

Reputation: 500

Design pattern for doing a sequence of operations on an I/O based object

I find myself frequently writing code that follows the following sequence:

  1. Attempt to open an I/O based resource (i.e. from a database and/or file). This could potentially fail.
  2. If successful, perform a series of operations via the object.
    • These operations change based on user input. For example: filter data, do calculation A, do calculation B, write the results somewhere.
    • Each of these operations could potentially fail and return with an error message. If a failure occurs at any time, the sequence should abort.
    • Some operations must be done before others: writing results cannot occur before calculations, for example.
  3. Close the resource.

Is there a design pattern that would work well for a flow like this? When I try to wrap the above sequence into a class, the immediate problems I always run into are:

Upvotes: 2

Views: 322

Answers (1)

Richard Hodges
Richard Hodges

Reputation: 69892

An organisation refusing to allow exception handling is more often than not working on a set of false premises.

That being said, perhaps your organisation can be persuaded to allow limited use of exceptions if it can be proven that no exception can escape from the block of code in question:

#include <iostream>
#include <fstream>
#include <string>
#include <stdexcept>

struct exception_swallower
{
    void set_error(std::string msg)
    {
        success = false;
        message = std::move(msg);
    }
    bool success = true;
    std::string message;
};

std::ostream& operator <<(std::ostream& os, const exception_swallower& es)
{
    if (es.success) {
        return os << "success";
    }
    else {
        return os << "failure: " << es.message;
    }
}

// @pre stream shall be initialised with an r-value reference
// to a stream which has already had 'open' called on it.
//
template<class Stream, class Op>
exception_swallower perform_op_on_stream(Stream stream, Op op)
{
    // our non-exception return type
    exception_swallower ret;

    // catch all exceptions
    try {
        // check that stream did open
        if (!stream) {
            throw std::runtime_error("stream didn't open");
        }
        // perform the operations
        op(stream);
    }
    catch(const std::exception& e)
    {
        // reflect failures in the returned object
        ret.set_error(e.what());
    }
    return ret;
}

// some sample operations    
auto null_op = [](auto& ios)
{
    // do nothing
};

auto error_op = [](auto& ios)
{
    // throw an exception
    throw std::runtime_error("error in stream");
};


int main(int argc, char** argv)
{
    // note: the stream is created as a temporary, which
    // automatically yields an r-value reference.

    std::cout << perform_op_on_stream(std::ifstream(argv[0]), null_op) << std::endl;
    std::cout << perform_op_on_stream(std::ifstream(argv[0]), error_op) << std::endl;
}

expected output:

success
failure: error in stream

Upvotes: 1

Related Questions