当 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 依赖于 referencederived 持有的对象,这看起来像先有鸡还是先有蛋无法解决?

这里的继承只是错误的抽象吗?一个干净的替代品?

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_));
    }
};