web-dev-qa-db-ja.com

移動コンストラクターが継承されておらず、デフォルトも生成されていません

拡張してみましたstd::ifstreamバイナリ変数を読みやすくするための関数が1つあり、驚いたことにusing std::ifstream::ifstream; moveコンストラクターは継承されません。さらに悪いことに、それは明示的に削除されます。

#include <fstream>

class BinFile: public std::ifstream
{
public:
    using std::ifstream::ifstream;
    //BinFile(BinFile&&) = default; // <- compilation warning: Explicitly defaulted move constructor is implicitly deleted

    template<typename T>
    bool read_binary(T* var, std::streamsize nmemb = 1)
    {
        const std::streamsize count = nmemb * sizeof *var;
        read(reinterpret_cast<char*>(var), count);
        return gcount() == count;
    }
};

auto f()
{
    std::ifstream ret("some file"); // Works!
    //BinFile ret("some file"); // <- compilation error: Call to implicitly-deleted copy constructor of 'BinFile'
    return ret;
}

私はムーブコンストラクタを間違って感じているだけなので、明示的に実装したくありません。質問:

  1. なぜ削除されるのですか?
  2. 削除するのは理にかなっていますか?
  3. Moveコンストラクターが適切に継承されるようにクラスを修正する方法はありますか?
7
lvella

問題は、_basic_istream_(_basic_ifstream_のベースであり、テンプレートifstreamはインスタンス化です)virtuallyは_basic_ios_から継承し、_basic_ios_には(保護されたデフォルトコンストラクターに加えて)移動コンストラクターが削除されています。

(仮想継承の理由は、fstreamifstreamから継承するofstreamの継承ツリーにひし形があるためです。)

最も派生したクラスコンストラクターがその(継承された)仮想ベースコンストラクターを直接呼び出すことはほとんど知られていないか、または忘れられやすい事実です。base-or-member-init-listで明示的に呼び出さない場合、仮想ベースのdefaultコンストラクタが呼び出されます。ただし(これはさらにあいまいです)、暗黙的に定義またはデフォルトとして宣言されたコピー/移動コンストラクターの場合、選択された仮想基本クラスコンストラクターはnotデフォルトのコンストラクタですが、対応するコピー/移動コンストラクタです。これが削除されているかアクセスできない場合、最も派生したクラスのコピー/移動コンストラクターが削除済みとして定義されます。

次に例を示します(C++ 98までさかのぼります)。

_struct B { B(); B(int); private: B(B const&); };
struct C : virtual B { C(C const&) : B(42) {} };
struct D : C {
    // D(D const& d) : C(d) {}
};
D f(D const& d) { return d; } // fails
_

(ここでBは_basic_ios_に対応し、Cifstreamに対応し、DBinFileに対応します; _basic_istream_デモンストレーションには不要です。)

Dの手巻きコピーコンストラクターのコメントを外すと、プログラムはコンパイルされますが、B::B()notB::B(int)。これが、明示的に許可を与えていないクラスから継承することが悪い考えである理由の1つです。そのコンストラクタが最も派生したクラスコンストラクタとして呼び出された場合、継承元のクラスのコンストラクタによって呼び出されるのと同じ仮想ベースコンストラクタを呼び出さない可能性があります。

Libstdc ++とlibcxxの両方で_basic_ifstream_のmoveコンストラクターが_basic_ios_のデフォルト以外のコンストラクターを呼び出さないので、あなたができることについては、手書きのmoveコンストラクターが機能すると思います_basic_streambuf_ポインターからの1つですが、代わりにコンストラクター本体で初期化します(これは [ifstream.cons]/4 が言っているようです)。他の潜在的な問題については、 継承によるC++標準ライブラリの拡張? を読む価値があります。

4
ecatmur

前の回答で述べたように、コンストラクターは基本クラスで削除済みとして定義されています。つまり、使用できません-<istream>を参照してください。

__CLR_OR_THIS_CALL basic_istream(const basic_istream&) = delete;
basic_istream& __CLR_OR_THIS_CALL operator=(const basic_istream&) = delete;

また、return retは、移動コンストラクタではなく、削除されたコピーコンストラクタを使用しようとします。

ただし、独自の移動コンストラクターを作成した場合は、機能するはずです。

BinFile(BinFile&& other) : std::ifstream(std::move(other))
{

}

これは質問のコメントの1つ(@ igor-tandetnik)で確認できます。

0
Juv