Steve’s Blog

Just another compsci weblog

Abstract factories: creating objects from strings

Occassionally I find myself in a situation where I’d like to instantiate objects whose types and parameters are given in a text file. Usually it’s the case that all the objects derive from a common base class, and so it’s not difficult to write a function that parses a set of parameters and passes them to the appropriate constructor, assuming that all constructors take the same set of parameters. This assumption, however, is not usually true in practice. So then the simplest solution becomes that each constructor should take as input a string to do its own parsing. This scheme is often wasteful because an integer parsed in one constructor is typically identically parsed in every other constructor (and similarly for other types). So it would be convenient if we had one function that parsed an arbitrary set of parameters and passed them to the appropriate constructor, automatically. This would eliminate code reuse which would reduce the risk of creating bugs. Finally, with variadic templates, this is possible in C++.

The key to this approach is constructing the function, which we’ll call applystream, that reads variables off an input stream and calls an arbitrary function with those variables. The trick here is, as is often the case with variadic template functions, to handle one argument and recursively call the function with the remaining arguments. Each time we will create a lambda function so that we may curry the call our arbitrary function (which will be a function calling a constructor, but the code is general so it could be anything). Note that I’ve assumed that we want any bare pointers wrapped in shared_ptr for
safety.

// base case
template
FuncRet applystream(std::function f, std::istream & in) {
        return f();
}

template
FuncRet applystream(std::function f, std::istream & in) {
	// Pull argument from the stream.
	// You must have the function istream& operator>>(istream&, Arg1) defined
	// for all types in Args... (this is always the case with primitives).
        Arg1 arg1;
        in >> arg1;

        if(in) {
		// If the stream is still valid, construct our lambda function.
		// The lambda function g takes n-1 arguments. It calls the
		// function passed in, f, with the curried argument arg1.
                std::function g =
                        [f, arg1](Args... args) -> FuncRet
                                { return f(arg1, args...); };
		// We pass the reduced function, g, to applystream recursively.
                return applystream(g, in);
        } else {
		// otherwise signal an error
                throw std::runtime_error("stream error");
        }
}

All we need to do is call applystream with the appropriate version of make_shared to construct the object. I’ve written a small example file that demonstrates a completely generic factory class that instantiates objects from an input stream using applystream. The factory is simply an abstract base class (to enable storage in a map or other stl container) plus a templated child class. By instantiating the child class, the compiler has everything it needs to know about how to create the class — you can simply use the function call operator to create a new instance based on an input stream. The link to the code is here.

No comments

No comments yet. Be the first.

Leave a reply