|
|||||||||||||||||||
HOME | COURSES | TALKS | ARTICLES | GENERICS | LAMBDAS | IOSTREAMS | ABOUT | CONTACT | | | | |||||||||||||||||||
|
Deriving from IOStreams
|
||||||||||||||||||
Deriving from IOStreams Using the IOStreams Framework to Define Specialized Streams
C++ Report, September 1995
Introduction. One common way to make use of the IOStreams framework is by deriving from its classes in order to define specialized streams for special purposes. Such kind of user defined streams will usually be derived from the 'ios' class family, e.g. class 'istream' or 'ostream'. Very often it will be necessary to define a specialized stream buffer, too, which will be derived from class 'streambuf' or its descendants. (An example for this way of using IOStreams can be found in [1].) However, these derivations are not completely trivial and there is a certain pitfall that may not be obvious to every IOStreams user. In this article we want to spend a few thoughts on where the pitfall actually is and take a deeper look at several ways to solve the problem. We will consider both derivation of a specialized stream and deriving a corresponding stream buffer to be used with the stream. Eventually we will address the forthcoming ANSI/ISO standard and show how to tackle the whole issue when using the standard IOStreams in the future.
Deriving a New Stream. Let's start with the following approach. (Basically it is the kind of solution Cay Horstmann chose in his article.) Approach 1. The idea is to define a specialized stream buffer together with a specialized output stream. The user defined stream will use the user defined stream buffer as its buffer component: class MyStreamBuf : public streambuf {On construction of 'MyOutputStream_1' the respective constructors will be executed as listed below: ios(0)Remark: In this list the constructors are ordered according to the instant of their completion, i.e. the constuctor which is completely executed first will be the first in this list. E.g. the constructor MyOutputStream_1() will be the last constructor to have accomplished its task, so it is the last one in this list. The idea behind this kind of ordering is that only after completion of a constructor the constructed object is ensured to be valid. In analogy the destructors will be ordered by the instant when their execution starts, i.e. the moment when the object to be destroyed becomes invalid. Considering the order of construction listed above it is visible that by the time ios() or ostream() are executed, the buffer component 'msb' will not yet be constructed. It follows that neither ios() nor ostream() must access the buffer. In fact, the pointer handed over to the stream on its construction points to a still invalid object. The same observation holds for destruction of a 'MyOutputStream_1'. Destructors are called in reverse order which means that neither ~ostream() nor ~ios() must access the stream buffer as it will already be destroyed. The approach outlined above is a widely used solution in practice. Surprisingly enough it actually works with all currently available IOStreams implementations known to the authors. This is possible because the IOStream implementations have constructors for class 'ios' and class 'ostream' that do not access the stream buffer at all. The destructors of class 'ios' or class 'ostream' on the other hand do access the stream buffer in some IOStreams implementations. Nevertheless is works because the raw data of the 'MyOutputStream_1' object is still available when ~ios() and ~ostream() are executed and ~MyStreamBuffer() usually does not really invalidate its data. Yet the approach seems to be hazardous because it relies on access to destroyed objects. Already tiny changes can break the whole code. Consider for example a change to MyStreamBuf so that it allocates data from the heap. On destruction of the buffer the heap data will probably be deleted which means a real invalidation as the storage management most likely will overwrite some data. The use of the MyStreamBuf data member in ~ios() or ~ostream() will in this case cause an error if by chance the use includes access to the deleted heap data. So what can we do? Is there another, more stable solution conceivable? Of course, there is. Approach 2. The idea is to allocate the stream buffer from the heap instead of using a data member of the stream object. class MyStreamBuf : public streambuf {Just to remind readers to the innards of IOStreams: 'bp' is a protected member of class 'ios' which holds the pointer to the respective stream buffer. sync() is a virtual member function of class 'streambuf' which is responsible for cleanup activities. On construction of 'MyOutputStream_2' the constructors will be executed as listed below: ios(0)This time ostream() receives a pointer to a valid, fully constructed stream buffer object. Thanks to the "delete bp;" in ~MyOutputStream_2() destructors are called in the following order: ~MyOutputStream_2()Again neither ~ostream() nor ~ios() may access the stream buffer via 'bp' anymore. This time it would be really lethal as 'bp' is assured to point to invalid data. So, why does this work? It turns out that a buffer pointer that equals null has special semantics in some IOStreams implementations: It indicates that the stream buffer is not valid and the stream classes pay attention to this particular situation and do not access it if "bp == 0". (This explains why ~MyOutputStream_2() set "bp = 0;".) Yet this is a solution that relies on peculiarities of the respective IOStream implementation and is not at all portable. AT&T's IOStreams has this undocumented 'feature', others may not have ... Regarding portability ... The ANSI/ISO standard ensures that neither ~ios() nor ~ostream() will access the stream buffer on destruction. Hence "bp == 0" will not be necessary anymore. The whole problem evaporates in a sudden. See approach 4 below for further details. But, we don't have a standard IOStream yet. So, is there a way to solve the problem in a stable AND portable way? Approach 3. What about providing the stream buffer via private inheritance rather than allocating it from the heap or using a data member of the stream object? class MyStreamBuf : public streambuf {Note that this order of executing the constructors is due to the 'virtual' private derivation from streambuf. See [2] ARM Section 12.6.2.: Virtual bases are constructed before any nonvirtual bases and in the order they appear on a depth-first left-to-right traversal of a directed acyclic graph of base classes; "left-to-right" is the order of appearance of the base class names in the declaration of the derived class. Obviously we have solved all problems, both on construction as well as on destruction: The stream buffer is constructed before any of the stream constructors is executed. As the destructors are called in reverse order the corresponding is true for the destructors: The stream buffer is destroyed no sooner than all stream destructors have been executed. Eventually we've found a stable and portable way to provide the stream buffer. The downside of this approach is that it uses private virtual inheritance, which is no problem in itself. But it is conceptually strange. The virtual derivation in this case is necessary only to attain this particular order of construction/destruction, and for no other reason. What does the idiom of 'private virtual inheritance' mean ??? Certainly more than forcing the compiler to call destructors in a specific order. Another philosophical remark about this solution: Private inheritance is frequently seen as conceptually equal to membership. This example proves it to be wrong: Membership, as used in approach 1, does not solve the problem, but private inheritance does! Using the ANSI/ISO Standard IOStreams. (For the future.) The ANSI/ISO standard will ensure that neither ~ios() nor ~istream()/~ostream() will access the stream buffer. The policy used by the IOStreams classes contained in the standard library is as follows: Only those descendants from class 'ios' which really provide a stream buffer may access the stream buffer on construction and destruction, for purpose of calling sync() for example. (This affects classes like e.g. 'ofstream' and 'ifstream', as they provide a 'filebuf' object to be used as stream buffer.) All its base classes, which do not provide the stream buffer but make use of an externally provided buffer, must and will not touch the buffer on construction and destruction. (This concerns the classes 'ios', 'istream' and 'ostream'.) What seems sensible and sound for the standard IOStreams classes will surely be good for most other IOStreams classes as well. We suggest to adopt this policy when deriving new classes from IOStreams. It fits neatly into the IOStreams framework and makes your new stream classes easy to comprehend. Let's glance over the 3 solutions cosidered above. Will they still be sensible in the presence of the standard IOStreams? Basically the problem was access to an already deleted stream buffer by one of the base classes' destructors for purpose of calling sync(). Now, when using the standard IOStreams, there will be no such problem any more. Neither ~ios() nor ~ostream() will access the already deleted stream buffer. Following the general policy described above, the assignment of caring about synchronisation on destruction of a stream will be taken by the new stream class's destructor. All three solutions make use of a specialized stream buffer. To make it a bit more exciting, let's say it overwrites sync(): class MyStreamBuf : public streambuf {Approach 1. class MyOutputStream_1 : public ostream {Remark: The function pubsync() is part of class streambuf's public interface and does no more than calling the private virtual sync() function. Approach 2. The previously necessary assignment 'sb = 0;' after having deleted the stream buffer is not needed anymore. class MyStreamBuf : public streambuf {Approach 3. With this approach it was necessary to make the private streambuf base class a virtual base class in order to achieve a particular order of destructor calls. This is no longer necessary, so we can dispense with the virtual derivation and use ordinary private inheritance which is much more intuitive anyway. class MyOutputStream_3 : private MyStreamBuf, public ostream {Conclusion. By and large, using the standardized IOStreams will lead to portable solutions in a much easier and more intuitive way. We faced the situation that we wanted to derive a new stream class which 'has-a' new stream buffer. We considered three approaches to provide the stream buffer: as a data member of the stream class, as a non-shared reference to a stream buffer on the heap and by private inheritance. With existing IOStream implementations there were subtle differences between the three approaches and we had to carefully pay attention to peculiarities of the various IOStream implementations. Hence, finding a portable solution was not trivial. Quite the converese with the standard IOStreams. All three approaches intuitively yield a correct and portable solution. No further headache required. That's what a standard is for, isn't it?
Deriving a New Stream Buffer. So far we have focused on deriving a new stream class and skillfully omitted all details about deriving the new stream buffer class. Will that be a similar hassle? Well, let's see ... There is a certain analogy between deriving a new stream class and deriving a new stream buffer class: Both class families have a resource which can or must be provided externally. For the 'ios' class family this resource is the stream buffer. It has to be provided externally and a pointer to the stream buffer is handed over via an according constructor. For the 'streambuf' class family this resource is the underlying character buffer, which is nothing but an ordinary character array. It is usually allocated by the stream buffer itself. Still, there is the possibility to provide it externally and hand over a pointer to the external buffer via a call to the setbuf() function. Given the latter situation we face an equivalent problem with stream buffers as we have discussed with streams: Do the 'streambuf' classes access the character buffer on construction or destruction? Construction is no issue as ther is no suitable 'streambuf' constructor which can take a pointer to the buffer as a parameter. You have to attach the external character buffer explicitly after construction by calling setbuf(). Fine! But what about destruction? Let's go and look for solutions! The only sensible reason for providing the character buffer externally we can conceive of is to have influence on the buffer size at runtime. (Surely one could contrive other reasons though.) Hence we will only consider solutions which allocate the character buffer dynamically at runtime from the heap. Approach 1. One can allocate the character buffer from the heap: class MyStreamBuf_1 : public streambuf {Again we face a problem here. We can't know what our IOStream implementation does on destruction of a stream buffer. It might chose to access its character buffer ... It follows, that this again is a non-portable approach. Approach 2. Another idea is to provide the character buffer via private inheritance: class MyStreamBufResources {The order of destruction will be: ~MyStreamBuf_2()Hence, whatever ~streambuf() choses to do to its character buffer we will have no problem as the buffer will still be "alive". Using the ANSI/ISO Standard IOStreams. (For the future.)
At present it is not yet specified what streambuf::setbuf()
in the standard IOStreams will be supposed to do. But there will still
be a protected virtual setbuf() function and a public non-virtual pubsetbuf()
function. So, for reasons of retaining compatibilty to existing IOStreams
one shouldn't expect any dramatic changes.
Summary. The problem discussed above is basically about providing a resource to a class, that itself only holds a pointer to the resource and accesses the resource via this pointer. As soon as the resource is provided externally, one has to ensure that that the resource is valid whenever the class decides to access the resource. The really crucial point is access on construction and destruction as the resource might not yet or already be deleted.
A solution which always will work is wrapping the resource
into a class and provide the resource via private derivation from the wrapper
class.
References. [1] Cay Horstmann. Extending the iostream library. C++ Report, May 1994
[2] M. Ellis & B. Stroustrup. The Annotated C++ Reference
Manual, Addison-Wesley, 1990.
|
|||||||||||||||||||
© Copyright 1995-2003 by Angelika Langer. All Rights Reserved. URL: < http://www.AngelikaLanger.com/Articles/C++Report/IOStreamsDerivation/IOStreamsDerivation.html> last update: 22 Oct 2003 |