This is part 14/14 of my Implementing
IXmlWriter post series.
Today I will add support for writing the generated XML to a C++ stream to last time’s
Finally the reason why I’ve insisted on calling this series
IXmlWriter (instead of
StringXmlWriter) should become clear: I’ve been planning on supporting writing the generated XML to more than just a string. Specifically, today I will add the ability to write the XML to a C++
ostream object, a base class in the C++ iostream library which defines a writable stream.
To this end, I decided to write a pure-virtual interface called
IXmlWriter which contains all the relevant methods for generating XML (e.g.
WriteComment(), etc.), and two concrete classes which implement this interface:
StringXmlWriter (which writes to a string as before) and
OstreamXmlWriter (which writes to a user-provided
ostream instance). I moved the XML-generating logic from
OstreamXmlWriter and implemented
StringXmlWriter in terms of
OstreamXmlWriter and a
The area where I probably spent the most time was deciding how to pass the
ostream instance to
OstreamXmlWriter and how
OstreamXmlWriter would store it, if necessary. I came up with the following options:
OstreamXmlWriteris passed a copy (or const reference) of the
ostreaminstance in its constructor and stores a copy of the
ostreaminstance as a member variable. Advantages: The typical idiom for most variables passed in the constructor. Disadvantages: For correctness, this requires a semantically correct copy constructor which is very difficult, and often impossible, to write for streams.
OstreamXmlWriteris passed a reference (pointer) to the
ostreaminstance in its constructor and stores a reference (pointer) to the
ostreaminstance as a member variable. Advantages: Relatively simple. Disadvantages:
OstreamXmlWriterbecomes implicitly tied to the lifetime of the passed object, which means that a user must be sure that
OstreamXmlWriteris destroyed before the
ostreaminstance is. Code such as
stringstream* ss = new stringstream(); XmlWriter w(ss); delete ss; w.WriteStartElement("blah");may result in hard-to-find bugs.
OstreamXmlWriteris passed a smart pointer object (e.g.
boost::shared_ptr) which points to the
ostreaminstance in its constructor and stores a copy of the smart pointer object as a member variable. Advantages: Lifetime issues are handled correctly. Disadvantages: The
ostreaminstance must be constructed on the heap and never the stack. We expose a dependency on a smart pointer implementation that users probably shouldn’t care about. (Alternative: Take a heap-constructed
ostream*in the constructor, store as a smart pointer member variable. However, what if a stack-constructed
OstreamXmlWritertakes a reference to the
ostreaminstance as an extra parameter for every XML writing function. Advantages: Parallels
operator<<, no lifetime issues. Disadvantages: Breaks
Based on these observations, I decided to go with (2). Here’s the new test case:
std::stringstream ss; OstreamXmlWriter xmlWriter(ss, StringXmlWriter::Formatting_Indented); xmlWriter.WriteComment("comment"); xmlWriter.WriteStartElement("root"); xmlWriter.WriteElementString("child", "value"); xmlWriter.WriteComment("comment"); xmlWriter.WriteStartElement("child"); xmlWriter.WriteAttributeString("att", "value"); xmlWriter.WriteEndElement(); xmlWriter.WriteStartElement("child"); xmlWriter.WriteStartElement("child"); xmlWriter.WriteStartElement("child"); xmlWriter.WriteEndDocument(); // ss.str() should equal (whitespace is important): // <!--comment--> // <root> // <child>value</child> // <!--comment--> // <child att="value"/> // <child> // <child> // <child/> // </child> // </child> // </root>
Because the source code is starting to get unwieldy (chorus: Too late!), I have linked to the source code files rather than insert the source code in-line with the post. Here’s the latest source code:
Unfortunately, the source code for this post has been lost.