当 Base 构造函数依赖于 Derived 的引用时构造函数初始化顺序
constructor initialisation order when Base constructor depends on reference from Derived
我有一个基础 class,它可以将对象写入带有缓冲的 std::ostream
。我想用
来称呼它
Obj obj;
flat_file_stream_writer<Obj> writer(std::cout);
writer.write(obj);
writer.flush();
还有
Obj obj;
flat_file_writer<Obj> writer("filename.bin");
writer.write(obj);
writer.flush();
后一个调用必须建立一个std::ofstream
实例,并在写入期间保持该实例,然后才能调用前一个版本。
我认为简单的非虚拟继承在这里就可以了。但是我有构造函数初始化顺序问题。
warning: field 'ofstream_' will be initialized after base
'flat_file_stream_writer<hibp::pawned_pw>' [-Wreorder-ctor]
因为基数 class 依赖于 reference 到 derived 持有的对象,这看起来像先有鸡还是先有蛋无法解决?
这里的继承只是错误的抽象吗?一个干净的替代品?
template <typename ValueType>
class flat_file_stream_writer {
public:
explicit flat_file_stream_writer(std::ostream& os, std::size_t buf_size = 100)
: db_(os), buf_(buf_size) {}
void write(const ValueType& value) {
if (buf_pos_ == buf_.size()) flush();
std::memcpy(&buf_[buf_pos_], &value, sizeof(ValueType));
++buf_pos_;
}
void flush() { // could also be called in destructor?
if (buf_pos_ != 0) {
db_.write(reinterpret_cast<char*>(buf_.data()), // NOLINT reincast
static_cast<std::streamsize>(sizeof(ValueType) * buf_pos_));
buf_pos_ = 0;
}
}
private:
std::ostream& db_;
std::size_t buf_pos_ = 0;
std::vector<ValueType> buf_;
};
template <typename ValueType>
class flat_file_writer : public flat_file_stream_writer<ValueType> {
public:
explicit flat_file_writer(std::string dbfilename)
: dbfilename_(std::move(dbfilename)), dbpath_(dbfilename_),
ofstream_(dbpath_, std::ios::binary), flat_file_stream_writer<ValueType>(ofstream_) {
if (!ofstream_.is_open())
throw std::domain_error("cannot open db: " + std::string(dbpath_));
}
private:
std::string dbfilename_;
std::filesystem::path dbpath_;
std::ofstream ofstream_;
};
您可以将 std::ofstream ofstream_;
放在一个单独的结构中,然后让 flat_file_writer
使用 private 从该结构继承。 Base classes 按照声明的顺序进行初始化,因此请确保在 flat_file_stream_writer
之前从该结构继承。您现在可以在基础 class flat_file_stream_writer
.
之前初始化 ofstream_
仔细检查后,我认为您可能希望将所有 3 个成员都放入结构中。
请参阅下面的更新代码。
所以这实际上意味着 ofstream_holder
中的 3 个成员现在位于 flat_file_writer
布局中的 flat_file_stream_writer
之前。
这似乎是正确的解决方案,不仅因为它“消除了编译器警告”,还因为现在我们真正获得了正确的初始化顺序,即先构造 std::ofstream 和 然后 将对它的引用传递给 flat_file_stream_writer
。在 destruct 期间 std::ofstream
将被销毁 last.
上面的原始代码完全错了,因为我们在派生构造函数初始化器中编写的顺序被忽略了,实际实现的顺序是布局顺序,即声明成员的顺序。
(尽管原始代码存在缺陷,但它对我来说“运行 可以”使用消毒剂等……但可能是 UB?)。
template <typename ValueType>
class flat_file_stream_writer {
public:
explicit flat_file_stream_writer(std::ostream& os, std::size_t buf_size = 100)
: db_(os), buf_(buf_size) {}
void write(const ValueType& value) {
if (buf_pos_ == buf_.size()) flush();
std::memcpy(&buf_[buf_pos_], &value, sizeof(ValueType));
++buf_pos_;
}
void flush() {
if (buf_pos_ != 0) {
db_.write(reinterpret_cast<char*>(buf_.data()), // NOLINT reincast
static_cast<std::streamsize>(sizeof(ValueType) * buf_pos_));
buf_pos_ = 0;
}
}
private:
std::ostream& db_;
std::size_t buf_pos_ = 0;
std::vector<ValueType> buf_;
};
struct ofstream_holder {
explicit ofstream_holder(std::string dbfilename)
: dbfilename_(std::move(dbfilename)), dbpath_(dbfilename_),
ofstream_(dbpath_, std::ios::binary) {}
std::string dbfilename_;
std::filesystem::path dbpath_;
std::ofstream ofstream_;
};
template <typename ValueType>
class flat_file_writer : private ofstream_holder, public flat_file_stream_writer<ValueType> {
public:
explicit flat_file_writer(std::string dbfilename)
: ofstream_holder(std::move(dbfilename)), flat_file_stream_writer<ValueType>(ofstream_) {
if (!ofstream_.is_open())
throw std::domain_error("cannot open db: " + std::string(dbpath_));
}
};
我有一个基础 class,它可以将对象写入带有缓冲的 std::ostream
。我想用
Obj obj;
flat_file_stream_writer<Obj> writer(std::cout);
writer.write(obj);
writer.flush();
还有
Obj obj;
flat_file_writer<Obj> writer("filename.bin");
writer.write(obj);
writer.flush();
后一个调用必须建立一个std::ofstream
实例,并在写入期间保持该实例,然后才能调用前一个版本。
我认为简单的非虚拟继承在这里就可以了。但是我有构造函数初始化顺序问题。
warning: field 'ofstream_' will be initialized after base
'flat_file_stream_writer<hibp::pawned_pw>' [-Wreorder-ctor]
因为基数 class 依赖于 reference 到 derived 持有的对象,这看起来像先有鸡还是先有蛋无法解决?
这里的继承只是错误的抽象吗?一个干净的替代品?
template <typename ValueType>
class flat_file_stream_writer {
public:
explicit flat_file_stream_writer(std::ostream& os, std::size_t buf_size = 100)
: db_(os), buf_(buf_size) {}
void write(const ValueType& value) {
if (buf_pos_ == buf_.size()) flush();
std::memcpy(&buf_[buf_pos_], &value, sizeof(ValueType));
++buf_pos_;
}
void flush() { // could also be called in destructor?
if (buf_pos_ != 0) {
db_.write(reinterpret_cast<char*>(buf_.data()), // NOLINT reincast
static_cast<std::streamsize>(sizeof(ValueType) * buf_pos_));
buf_pos_ = 0;
}
}
private:
std::ostream& db_;
std::size_t buf_pos_ = 0;
std::vector<ValueType> buf_;
};
template <typename ValueType>
class flat_file_writer : public flat_file_stream_writer<ValueType> {
public:
explicit flat_file_writer(std::string dbfilename)
: dbfilename_(std::move(dbfilename)), dbpath_(dbfilename_),
ofstream_(dbpath_, std::ios::binary), flat_file_stream_writer<ValueType>(ofstream_) {
if (!ofstream_.is_open())
throw std::domain_error("cannot open db: " + std::string(dbpath_));
}
private:
std::string dbfilename_;
std::filesystem::path dbpath_;
std::ofstream ofstream_;
};
您可以将 std::ofstream ofstream_;
放在一个单独的结构中,然后让 flat_file_writer
使用 private 从该结构继承。 Base classes 按照声明的顺序进行初始化,因此请确保在 flat_file_stream_writer
之前从该结构继承。您现在可以在基础 class flat_file_stream_writer
.
ofstream_
仔细检查后,我认为您可能希望将所有 3 个成员都放入结构中。
请参阅下面的更新代码。
所以这实际上意味着 ofstream_holder
中的 3 个成员现在位于 flat_file_writer
布局中的 flat_file_stream_writer
之前。
这似乎是正确的解决方案,不仅因为它“消除了编译器警告”,还因为现在我们真正获得了正确的初始化顺序,即先构造 std::ofstream 和 然后 将对它的引用传递给 flat_file_stream_writer
。在 destruct 期间 std::ofstream
将被销毁 last.
上面的原始代码完全错了,因为我们在派生构造函数初始化器中编写的顺序被忽略了,实际实现的顺序是布局顺序,即声明成员的顺序。
(尽管原始代码存在缺陷,但它对我来说“运行 可以”使用消毒剂等……但可能是 UB?)。
template <typename ValueType>
class flat_file_stream_writer {
public:
explicit flat_file_stream_writer(std::ostream& os, std::size_t buf_size = 100)
: db_(os), buf_(buf_size) {}
void write(const ValueType& value) {
if (buf_pos_ == buf_.size()) flush();
std::memcpy(&buf_[buf_pos_], &value, sizeof(ValueType));
++buf_pos_;
}
void flush() {
if (buf_pos_ != 0) {
db_.write(reinterpret_cast<char*>(buf_.data()), // NOLINT reincast
static_cast<std::streamsize>(sizeof(ValueType) * buf_pos_));
buf_pos_ = 0;
}
}
private:
std::ostream& db_;
std::size_t buf_pos_ = 0;
std::vector<ValueType> buf_;
};
struct ofstream_holder {
explicit ofstream_holder(std::string dbfilename)
: dbfilename_(std::move(dbfilename)), dbpath_(dbfilename_),
ofstream_(dbpath_, std::ios::binary) {}
std::string dbfilename_;
std::filesystem::path dbpath_;
std::ofstream ofstream_;
};
template <typename ValueType>
class flat_file_writer : private ofstream_holder, public flat_file_stream_writer<ValueType> {
public:
explicit flat_file_writer(std::string dbfilename)
: ofstream_holder(std::move(dbfilename)), flat_file_stream_writer<ValueType>(ofstream_) {
if (!ofstream_.is_open())
throw std::domain_error("cannot open db: " + std::string(dbpath_));
}
};