Конструктор перемещения не наследуется и не генерируется по умолчанию - PullRequest
7 голосов
/ 02 мая 2020

Я попытался расширить std::ifstream одной функцией, чтобы было проще читать двоичные переменные, и, к моему удивлению, с using std::ifstream::ifstream; конструктор перемещения не наследуется. Хуже того, он явно удален.

#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. Есть ли способ исправить мой класс, чтобы переместить конструктор правильно унаследован?

Ответы [ 2 ]

4 голосов
/ 03 мая 2020

Проблема в том, что basic_istream (база basic_ifstream, из которых шаблон ifstream является экземпляром) фактически наследуется от basic_ios, а basic_ios имеет удаленный конструктор перемещения (в дополнение к защищенному конструктору по умолчанию).

(Причина виртуального наследования заключается в том, что в дереве наследования fstream есть ромб, который наследуется от ifstream и ofstream.)

Это малоизвестный и / или легко забываемый факт, что самый производный конструктор класса напрямую вызывает свои (унаследованные) виртуальные базовые конструкторы, и если он не делает этого явно в base-or-member-init-list тогда будет вызван конструктор default виртуальной базы. Однако (и это еще более непонятно) для конструктора копирования / перемещения, неявно определенного или объявленного по умолчанию, выбранный конструктор виртуального базового класса не конструктор по умолчанию, но соответствует конструктор копирования / перемещения; если он удален или недоступен, конструктор копирования / перемещения самого производного класса будет определен как удаленный.

Вот пример (который работает еще в 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, C - ifstream и D - вашему BinFile; basic_istream не требуется для демонстрации.)

Если свернутый вручную Конструктор копирования D не закомментирован, программа скомпилируется, но она будет вызывать B::B(), , а не B::B(int). Это одна из причин, почему плохая идея наследовать от классов, которые явно не дали вам разрешения на это; вы не можете вызывать тот же виртуальный базовый конструктор, который был бы вызван конструктором класса, от которого вы унаследовали, если бы этот конструктор был вызван как конструктор класса с наибольшим производным.

Что вы можете сделать, Я считаю, что рукописный конструктор перемещения должен работать, поскольку и в libstdc ++, и в libcxx конструктор перемещения basic_ifstream не вызывает конструктор не по умолчанию basic_ios (есть один из указателя basic_streambuf), но вместо этого инициализирует его в теле конструктора (похоже, это то, что говорит [ifstream.cons] / 4 ). Стоит прочитать Расширение стандартной библиотеки C ++ с помощью наследования? для других потенциальных ошибок.

0 голосов
/ 03 мая 2020

Как упоминалось в предыдущем ответе, конструктор определяется как удаленный в базовом классе. Это означает, что вы не можете его использовать - см. <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))
{

}

Это можно увидеть в одном из комментариев вашего вопроса (@ igor-tandetnik).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...