Holder

The Holder offers a framework for resource management of polymorphic objects. It enables the use of input and output operators, and provides a short-hand for dynamic casting. Using the Holder, it becomes easier to store polymorphic objects in a container than it would be using pointers.

The framework requires the user to implement three elements:

  1. The set of polymorphic classes inheriting from a chosen base class BaseClass;
  2. A Factory-method Factoryimplemented as a functor;
  3. An operator<<(std::ostream&, BaseClass&);

Interface

Holder

The Holder class takes two template parameters:

template <class BaseClass, class Factory>
class Holder;

  typedef BasePtr  
The type of pointer used to hold objects.
  Holder()  
Constructor, no object will be managed.
  Holder(BasePtr ptr)  
The object pointed to by ptr will be managed.
  Holder(BaseClass *ptr)  
The object pointed to by ptr will be managed.
  BasePtr &operator()()  
Returns a pointer to the object held.
  template <class DerivedClass>
  DerivedClass& cast()  
Return a reference to the object as a DerivedClass.
  template <class DerivedClass>  
  const DerivedClass& cast() const 
Return a reference to the object as a const DerivedClass.

Element 1: the Class Hierarchy

The class hierarchy is to be implemented freely, but it is recommended to include a virtual member function std::string() to_string() in the base class.

Element 2: the Factory

The functor Factory implementing the factory method should contain an implementation of either one of the two functions:

Base* operator()(std::istream&) throw(cvmlcpp::ParseError)
or:
Holder<Base, Factory>::BasePtr operator()(std::istream&) throw(cvmlcpp::ParseError)

Element 3: the Output Operator

The output operator should be implement as a compliment of Factory's operator()(std::istream&), that is, if output produced by the implemented operator<<(std::ostream&, BaseClass&); is given as input to the Factory, the object constructed by the Factory should be identical to object that produced the output. See the example.

Example

#include <cassert>
#include <string>
#include <iostream>
#include <sstream>

#include <boost/lexical_cast.hpp>

#include <cvmlcpp/base/Holder>
#include <cvmlcpp/volume/DTree>

/*
 * The framework consists of 3 elements
 *
 * 1) The set of polymorphic classes
 * 2) A Factory-method implemented as a Functor
 * 3) An operator<<(...)
 */

// First element: The set of polymorphic classes
struct Furniture
{
	int price;

	virtual ~Furniture() { }

	virtual std::string to_string() const = 0;
};

struct Chair : public Furniture
{
	int legs;
	virtual std::string to_string() const
	{
		return  std::string("<chair> <legs> ") +
			boost::lexical_cast<std::string>(legs) +
			std::string(" </legs> </chair>");
	}
};

struct Table : public Furniture
{
	int surface;
	virtual std::string to_string() const
	{
		return  std::string("<table> <surface> ") +
			boost::lexical_cast<std::string>(surface) +
			std::string(" </surface> </table>");
	}
};

// Second element: A Factory-method implemented as a Functor
class FurnitureFactory
{
	public:
		// The factory should produce an object from the class hierarchy
		// given an input stream. It may throw an exception of type
		// cvmlcpp::ParseError if the input is invalid.
		Furniture * operator()(std::istream& i_stream)
			throw(cvmlcpp::ParseError)
		{
			i_stream >> tag; // read open tag
			if (tag == "<chair>")
				return parseChair(i_stream);
			else if (tag == "<table>")
				return parseChair(i_stream);
			else throw (cvmlcpp::ParseError()); // Unknown tag

			assert(false); // We should never get here
			return (Furniture *)0;
		}

		Chair* parseChair(std::istream& i_stream)
			throw(cvmlcpp::ParseError)
		{
			Chair* chair = new Chair();

			// This is buggy but let's keep the example simple
			try {
				// Read "<legs> value </legs>"
				i_stream >> tag >> chair->legs >> tag;
				i_stream >> tag; // read "</chair>"
			}
			catch (std::exception &e) {
				delete chair;
				throw(cvmlcpp::ParseError());
			}

			return chair;
		}

		Table* parseTable(std::istream& i_stream)
			throw(cvmlcpp::ParseError)
		{
			Table* table = new Table();

			// This is buggy but let's keep the example simple
			try {
				// Read "<surface> value </surface>"
				i_stream >> tag >> table->surface >> tag;
				i_stream >> tag; // read "</table>"
			}
			catch (std::exception &e) {
				delete table;
				throw(cvmlcpp::ParseError());
			}

			return table;
		}

	private:
		std::string tag;
};

// Third element: an output operator
std::ostream& operator<<(std::ostream& o_stream, const Furniture &furniture)
{ return o_stream << furniture.to_string(); }

// Needed for test
bool operator==(const Chair& lhs, const Chair& rhs)
{ return lhs.legs == rhs.legs; }

// XML representation of a DTree describing a room
const char * roomXML =
"<branch> \
	<leaf> \
		<index> 0 </index> \
		<value> \
			<chair> <legs> 4 </legs> </chair> \
		</value> \
	</leaf> \
	<leaf> \
		<index> 1 </index> \
		<value> \
			<chair> <legs> 3 </legs> </chair> \
		</value> \
	</leaf> \
	<leaf> \
		<index> 2 </index> \
		<value> \
			<chair> <legs> 1 </legs> </chair> \
		</value> \
	</leaf> \
	<leaf> \
		<index> 3 </index> \
		<value> \
			<table> <surface> 10 </surface> </table> \
		</value> \
	</leaf> \
</branch>";

int main()
{
	using namespace std;
	using namespace cvmlcpp;

	// Description of an object
	const string chairxml = "<chair> <legs> 4 </legs> </chair>";

	// Create a stream
	stringstream input(chairxml);

	// Read an object from a stream and hold it
	Holder<Furniture, FurnitureFactory> holder1;
	input >> holder1;

	// Output the object
	stringstream output;
	output << holder1;

	// Read the object back
	Holder<Furniture, FurnitureFactory> holder2;
	output >> holder2;

	// Verify that they are equal
	assert(holder1.cast<Chair>() == holder2.cast<Chair>());

	// An XML-like source - the description of a room
	stringstream roomStream(roomXML);

	// Read a tree full of polymorphic objects from XML
	DTree<Holder<Furniture, FurnitureFactory>, 2> room;
	roomStream >> room;

	cout << room;

	return 0;
}