C++ 標準では、[lib.istream] に従って、実質的に から派生した [lib.iostream.format]std::istream
の typedef として名前を定義しています。一方、は[lib.iostream.forward] で の typedef として定義されているから仮想的に派生しています。したがって、両方の継承ブランチには、(別名) への仮想継承関係があります。std::basic_istream<char>
std::basic_ios<char>
gzstreambase
std::ios
std::basic_ios<char>
std::ios
std::basic_ios<char>
標準ライブラリの実装が壊れていない場合std::ios
、igzstream で 2 つのサブオブジェクトを取得するべきではありませんが、基本クラスを virtual と宣言すると、基本クラスの初期化の順序が変更されるため、さらなる結果が生じます。
クラス igzstream : public gzstreambase、public std::istream
仮想基本クラス (間接的なものであっても) が最初std::ios
に初期化されるため、最初に初期化され、次に初期化されますstd::ios_base
(それ自体の非仮想基本クラス)。次に、非仮想基本クラスが左から右の順序で初期化されるため、gzstreambase
最初にstd::istream
.
クラス igzstream : パブリック gzstreambase、仮想パブリック std::istream
仮想基本クラス (間接的なものであっても) が最初std::ios
に初期化されるため、最初に初期化され、次に初期化されますstd::ios_base
(それ自体の非仮想基本クラス)。次にstd::istream
、まだ別の仮想基底クラスであるため、初期化されますが、 が必要std::ios
であり、最後に gzstreambase
.
これを念頭に置いて、 igzstream のコンストラクターは、継承されたメンバー buf が初期化される前に、buf と呼ばれる gzstreambuf メンバーのアドレスをオブジェクトのコンストラクターに渡すため、からの仮想派生は非常に悪い考えstd::istream
のように思えると判断できます。std::istream
[lib.istream.cons]によると、おそらく問題の原因は をgzstreambase(consth char *, int)
呼び出しstd::ios::init()
、std::istream
コンストラクターが同じように動作することです。関数std::ios::init
ストリームを良好な状態で初期化することが文書化されています。そのため、istream サブオブジェクトが gzstreambase オブジェクトの後に初期化された場合、ios ベース オブジェクトの 2 番目の init で実際にエラー フラグがクリアされるはずです。これは実際には gzstream ライブラリのバグのようです。正しい構築順序 (最初に gzstreambuf、2 番目に istream、次にファイルを開こうとする) を取得することは、まったく重要な問題のように思えます。元のバージョンには「gzstreambuf、open、istream」があり、istream はオープンの失敗を上書きしますが、提案された修正には「istream、gzstreambuf、open」があり、istream はまだ構築されていない streambuf のアドレスを取得します。
gzstream の開始コンストラクターを使用しないことが回避策ですが、開始コンストラクターを修正するための適切な解決策を考え、結果がわかり次第回答を編集します。
誰に尋ねるかによって、複数の init 呼び出しが可能 ( 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 が使用する I/O ライブラリを提供) は、標準では複数回の呼び出しでの動作が指定されていないため、未定義の動作であると主張しています。
したがって、実用的な移植動作のために、init を繰り返し呼び出さないでください。しかし、これは gzstream で起こることです。これは実際には、C++ のような多重継承の反対者が正しいと思われる状況の 1 つです。「istream インターフェイス」を提供できるようにするには、std::istream を継承する必要がありますが、一方で、std::istream を継承する必要はありません。これは、コンストラクターが望ましくないことを行うためです。std::istream が「単なるインターフェース」である場合、gzstreambase から派生する実装と一緒に問題なく実装できます。
この場合の唯一の解決策は、open を実行する gzstreambase コンストラクターを削除し、igzstream および ogzstream コンストラクターに open 呼び出しを配置することです (したがって、open の呼び出しを複製します)。このようにして、init が istream/ostream コンストラクターで一度だけ呼び出されることに依存できます。