Я пытаюсь использовать gzstream 1.5 для разработки ios под xcode 6.1, libz.1.dylib.
Эта библиотека была написана довольно давно.
Я нашел это
class igzstream : public gzstreambase, public std::istream
должно быть
class igzstream : public gzstreambase, public virtual std::istream
То же самое для ogzstream.
Потому что, если файл не существует, первый вариант возвращает true для good () после инициализации. AFAIK это из-за двух предков std :: ios.
Интересно, это действительно ошибка и почему она еще не исправлена!
Стандарт C ++ определяет имя std::istream
как typedef из std::basic_istream<char>
в [lib.iostream.format], который фактически получен из std::basic_ios<char>
Согласно [lib.istream].
С другой стороны, gzstreambase
практически получен из std::ios
, который определен в [lib.iostream.forward] как typedef из std::basic_ios<char>
, Таким образом, обе ветви наследования имеют виртуальное отношение наследования к std::ios
(ака std::basic_ios<char>
).
Если ваша стандартная реализация библиотеки не нарушена, вы не должны получить два std::ios
подобъекты в igzstream, но объявление виртуального базового класса имеет дополнительные последствия, изменяя порядок инициализации базового класса.
класс igzstream: общедоступный gzstreambase, общедоступный std :: istream
Виртуальные базовые классы (даже косвенные) инициализируются первыми, поэтому std::ios
инициализируется первым, что, в свою очередь, инициализирует std::ios_base
(не виртуальный базовый класс сам по себе). Затем не виртуальные базовые классы инициализируются в порядке слева направо, поэтому gzstreambase
будет первый std::istream
,
класс igzstream: публичный gzstreambase, виртуальный публичный std :: istream
Виртуальные базовые классы (даже косвенные) инициализируются первыми, поэтому std::ios
инициализируется первым, что, в свою очередь, инициализирует std::ios_base
(не виртуальный базовый класс сам по себе). затем std::istream
инициализируется, поскольку это еще один виртуальный базовый класс, но требующий std::ios
, и наконец gzstreambase
,
Имея это в виду, вы можете определить, что виртуальный вывод из std::istream
похоже на очень плохая идея, потому что конструктор igzstream передает адрес своего члена gzstreambuf, называемого buf, конструктору std::istream
объект до того, как наследуемый член buf был инициализирован.
Вероятно, причина вашей проблемы в том, что gzstreambase(consth char *, int)
звонки std::ios::init()
и std::istream
конструктор ведет себя как будто он делает то же самое, согласно [lib.istream.cons]. Функция std::ios::init
задокументировано для инициализации потока в хорошем состоянии. Таким образом, если подобъект istream инициализируется после объекта gzstreambase, вторая инициализация базового объекта ios действительно должна очистить флаги ошибок. Это действительно похоже на ошибку в библиотеке gzstream. Правильный порядок построения (сначала gzstreambuf, затем istream, а затем попытка открыть файл) кажется совершенно нетривиальной проблемой. В исходной версии есть «gzstreambuf, open, istream», где istream перекрывает ошибку открытия, в то время как в предложенном вами исправлении есть «istream, gzstreambuf, open», где istream получает адрес еще не сконструированного потокового буфера.
Обходной путь — не использовать открывающий конструктор gzstream, но я подумаю над хорошим решением, чтобы исправить открывающий конструктор, и отредактирую ответ, как только я приду к результату.
В зависимости от того, кого вы спрашиваете, несколько вызовов инициализации в порядке (обычная интерпретация http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-closed.html#135) или не определены (http://article.gmane.org/gmane.comp.lib.boost.devel/235659). В компиляторе Microsoft многократный вызов init вызывает утечку памяти, и Dinkumware (который предоставляет библиотеку ввода-вывода, используемую Microsoft) настаивает на том, что стандарт не определяет поведение при множественных вызовах, поэтому это неопределенное поведение.
Так что для практического портативного поведения, не вызывайте init повторно. Но это то, что происходит в gzstream. На самом деле это одна из ситуаций, когда противники множественного наследования, как в C ++, кажутся правы. Вы делать нужно унаследовать std :: istream, чтобы иметь возможность предоставлять «интерфейс istream», в то время как с другой стороны, вам нужно не наследовать std :: istream, потому что его конструктор делает то, что вам не нужно. Если бы std :: istream был «просто интерфейсом», вы могли бы реализовать его вместе с получением реализации из gzstreambase без проблем.
Единственное решение, которое я вижу в этом случае — это удаление конструктора gzstreambase, выполняющего open, и помещение вызовов open в конструкторы igzstream и ogzstream (таким образом дублируя вызов open). Таким образом, можно полагаться на то, что init вызывается один раз и только один раз в конструкторе istream / ostream.
Возможный обходной путь без изменения библиотеки — использовать конструктор по умолчанию и открыть файл позже.
Пример:
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;
Результаты:
error initializing with non-existent file 0
error opening with non-existent file 1