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 IXmlWriter
.
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. WriteStartElement()
, 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 StringXmlWriter
to OstreamXmlWriter
and implemented StringXmlWriter
in terms of OstreamXmlWriter
and a stringstream
instance.
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:
OstreamXmlWriter
is passed a copy (or const reference) of theostream
instance in its constructor and stores a copy of theostream
instance 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.OstreamXmlWriter
is passed a reference (pointer) to theostream
instance in its constructor and stores a reference (pointer) to theostream
instance as a member variable. Advantages: Relatively simple. Disadvantages:OstreamXmlWriter
becomes implicitly tied to the lifetime of the passed object, which means that a user must be sure thatOstreamXmlWriter
is destroyed before theostream
instance is. Code such asstringstream* ss = new stringstream(); XmlWriter w(ss); delete ss; w.WriteStartElement("blah");
may result in hard-to-find bugs.OstreamXmlWriter
is passed a smart pointer object (e.g.std::auto_ptr
,boost::shared_ptr
) which points to theostream
instance in its constructor and stores a copy of the smart pointer object as a member variable. Advantages: Lifetime issues are handled correctly. Disadvantages: Theostream
instance 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-constructedostream*
in the constructor, store as a smart pointer member variable. However, what if a stack-constructedostream
is passed?)OstreamXmlWriter
takes a reference to theostream
instance as an extra parameter for every XML writing function. Advantages: Parallelsoperator<<
, no lifetime issues. Disadvantages: BreaksIXmlWriter
-as-interface.
Based on these observations, I decided to go with (2). Here’s the new test case:
|
|
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.