我尝试扩展 std::ifstream
使用一个函数可以更轻松地读取二进制变量,令我惊讶的是,使用 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;
}
我不想显式实现 move 构造函数,因为它只是感觉不对。问题:
最佳答案
问题是basic_istream
( basic_ifstream
的基础,其中模板 ifstream
是实例化)实际上继承自 basic_ios
, 和 basic_ios
有一个删除的 move 构造函数(除了 protected 默认构造函数)。
(虚拟继承的原因是 fstream
的继承树中有一个菱形,继承自 ifstream
和 ofstream
。)
一个鲜为人知和/或容易忘记的事实是,最派生的类构造函数直接调用其(继承的)虚基构造函数,如果它没有在基或成员初始化列表中明确这样做,则虚拟基的默认值构造函数将被调用。然而(这更加模糊),对于隐式定义或声明为默认的复制/move 构造函数,选择的虚拟基类构造函数不是默认构造函数,而是对应的复制/move 构造函数;如果它被删除或不可访问,则最派生的类复制/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_ios
, C
对应于 ifstream
和 D
对应于您的 BinFile
; basic_istream
对于演示来说是不必要的。)如果 D 的手动复制构造函数没有注释,程序会编译但会调用
B::B()
,不是 B::B(int)
.这就是为什么从没有明确授予您这样做的权限的类继承是一个坏主意的原因之一;如果该构造函数被称为最派生类构造函数,则您可能不会调用由您继承的类的构造函数调用的相同虚拟基构造函数。至于你能做什么,我相信手写的 move 构造函数应该可以工作,因为在 libstdc++ 和 libcxx 中
basic_ifstream
的 move 构造函数|不调用 basic_ios
的非默认构造函数(有一个,来自 basic_streambuf
指针),而是在构造函数体中初始化它(看起来这就是 [ifstream.cons]/4 所说的)。值得一读Extending the C++ Standard Library by inheritance?对于其他潜在的问题。
关于c++ - move 构造函数不是继承的,也不是默认生成的,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61563961/