移动构造函数不是继承的,也不是默认生成的

Move constructor not inherited nor default generated

我尝试用一​​个函数扩展 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. 有没有办法修复我的 class 以便正确继承移动构造函数?

问题是 basic_istreambasic_ifstream 的基础,其中模板 ifstream 是一个实例)实际上 继承自 basic_ios,并且 basic_ios 有一个删除的移动构造函数(除了受保护的默认构造函数)。

(虚拟继承的原因是fstream的继承树中有一颗菱形继承自ifstreamofstream。)

鲜为人知 and/or 很容易被遗忘的事实是,最派生的 class 构造函数直接调用其(继承的)虚拟基类构造函数,如果它没有在基类或-member-init-list 然后将调用虚拟基的 default 构造函数。但是(这更加模糊),对于隐式定义或声明为默认的 copy/move 构造函数,选择的虚拟基础 class 构造函数是 而不是 默认构造函数但是是对应的copy/move构造函数;如果它被删除或不可访问,最派生的 class copy/move 构造函数将被定义为已删除。

这是一个示例(最早可追溯到 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_iosC对应ifstreamD对应你的BinFilebasic_istream对于演示来说是不必要的。)

如果取消注释 D 的手动复制构造函数,程序将编译但会调用 B::B() 而不是 B::B(int)。这就是为什么从 classes 继承一个没有明确授予您这样做的权限的坏主意的原因之一;如果该构造函数被称为最派生的 class 构造函数,则您可能不会调用您正在继承的 class 的构造函数调用的相同虚拟基构造函数。

至于你能做什么,我相信手写的移动构造函数应该可以工作,因为在 libstdc++ 和 libcxx 中,basic_ifstream 的移动构造函数不会调用 [= 的非默认构造函数14=](有一个,来自 basic_streambuf 指针),而是在构造函数主体中初始化它(看起来这就是 [ifstream.cons]/4 is saying). It would be worth reading Extending the C++ Standard Library by inheritance? 其他潜在问题的原因。

正如前面的回答提到的,构造函数被定义为在基 class 中删除。 这意味着,您不能使用它 - 请参阅 <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) 的评论之一中看到这一点。