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:
- The set of polymorphic classes inheriting from a chosen base class
BaseClass;
- A Factory-method Factoryimplemented as a functor;
- 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;
}
|