gzstream lib opening not existing file

417 views Asked by At

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!

2

There are 2 answers

1
Michael Karcher On BEST ANSWER

The C++ standard defines the name std::istream as typedef of std::basic_istream<char> in [lib.iostream.format], which is virtually derived from std::basic_ios<char>, according to [lib.istream]. On the other hand, gzstreambase is virtually derived from std::ios, which is defined in [lib.iostream.forward] as typedef of std::basic_ios<char>. So both inheritance branches have a virtual inheritance relation to std::ios (aka std::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.

class igzstream : public gzstreambase, public std::istream

Virtual base classes (even indirect ones) are initialized first, so std::ios is first to be initialized, which in turn initializes std::ios_base (a non-virtual base-class of itself). Then non-virtual base-classes are initialized in left-to-right order, so gzstreambase first, then std::istream.

class igzstream : public gzstreambase, virtual public std::istream

Virtual base classes (even indirect ones) are initialized first, so std::ios is first to be initialized, which in turn initializes std::ios_base (a non-virtual base-class of itself). Then std::istream is initialized, as it still is another virtual base class, but needing std::ios, and finally gzstreambase.

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 the std::istream object before the inherited member buf has been initialized.

Probably the cause of your problem is that gzstreambase(consth char *, int) calls std::ios::init(), and the std::istream constructor behaves as-if it does the same, according to [lib.istream.cons]. The function std::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.

0
dmbailey On

A possible work-around without changing the library is to use the default constructor and open the file later.

Example:

igzstream noErrorFile("nonExistentFile");  // no error
cout << "error initializing with non-existent file " << noErrorFile.fail() << endl;
igzstream errorFile;
errorFile.open("nonExistentFile");  // error
cout << "error opening with non-existent file " << errorFile.fail() << endl;

results:

error initializing with non-existent file 0
error opening with non-existent file 1