I'm trying to use gzstream 1.5 for ios development under xcode 6.1, libz.1.dylib.
This library was written quite long time ago.
I've found that
class igzstream : public gzstreambase, public std::istream
should be
class igzstream : public gzstreambase, public virtual std::istream
Same for ogzstream.
Because if file doesn't exist, first variant returns true for good() after initializing. AFAIK it's because of two ancestors std::ios.
I wonder is it really a bug and why it wasn't fixed yet!
The C++ standard defines the name
std::istream
as typedef ofstd::basic_istream<char>
in [lib.iostream.format], which is virtually derived fromstd::basic_ios<char>
, according to [lib.istream]. On the other hand,gzstreambase
is virtually derived fromstd::ios
, which is defined in [lib.iostream.forward] as typedef ofstd::basic_ios<char>
. So both inheritance branches have a virtual inheritance relation tostd::ios
(akastd::basic_ios<char>
).If your standard library implementation is not broken, you should not get two
std::ios
subobjects in igzstream, but declaring the base class virtual has further consequences, by changing the order of base class initialization.Virtual base classes (even indirect ones) are initialized first, so
std::ios
is first to be initialized, which in turn initializesstd::ios_base
(a non-virtual base-class of itself). Then non-virtual base-classes are initialized in left-to-right order, sogzstreambase
first, thenstd::istream
.Virtual base classes (even indirect ones) are initialized first, so
std::ios
is first to be initialized, which in turn initializesstd::ios_base
(a non-virtual base-class of itself). Thenstd::istream
is initialized, as it still is another virtual base class, but needingstd::ios
, and finallygzstreambase
.With this in mind, you can determine that the virtual derivation from
std::istream
seems like a very bad idea, because the constructor of igzstream passes the address of its gzstreambuf member called buf to the constructor of thestd::istream
object before the inherited member buf has been initialized.Probably the cause of your problem is that
gzstreambase(consth char *, int)
callsstd::ios::init()
, and thestd::istream
constructor behaves as-if it does the same, according to [lib.istream.cons]. The functionstd::ios::init
is documented to initialize the stream in good condition. So if the istream subobject is initialized after the gzstreambase object, the second init of the ios base object should indeed clear the error flags. This actually looks like a bug in the gzstream library, indeed. Getting construction order right (gzstreambuf first, istream second and then trying to open the file) seems like a completely non-trivial problem. The original version has "gzstreambuf, open, istream" where istream clobbers the open failure, while your proposed fix has "istream, gzstreambuf, open" where istream gets the address of a not-yet-constructed streambuf.A workaround is to not use the opening constructor of gzstream, but I will think about a good solution to fix the opening constructor, and will edit the answer as soon as I come to a result.
Depending on who you ask, multiple init calls are OK (usual interpretation of http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-closed.html#135) or are undefined (http://article.gmane.org/gmane.comp.lib.boost.devel/235659). On the Microsoft compiler, calling init multiple times causes a memory leak, and Dinkumware (who provide the I/O library used by Microsoft) insists that the standard does not specify the behaviour on multiple calls, so it is undefinied behaviour.
So for practical portable behaviour, do not call init repeatedly. Yet this is what happens in gzstream. This is actually one of the situations opponents of multiple inheritance as in C++ seem to be right. You do need to inherit std::istream to be able to provide the "istream interface", while on the other hand, you need to not inherit std::istream, because its constructor does things you don't want. If std::istream were "just an interface", you could implement it alongside deriving implementation from gzstreambase without problems.
The only solution I see in this case is removing the gzstreambase constructor performing the open, and putting open calls in the igzstream and ogzstream constructors (thus duplicating the call to open). This way, one can rely on init being called once and only once in the istream/ostream constructor.