跳过层次结构中的中间类,并使用boost ::序列化

Skip intermediate classes in hierarchy inheritance with boost::serialization

本文关键字:boost 序列化 层次结构 中间      更新时间:2023-10-16

上下文:我有一个类似树的结构,代表我想使用 boost::serialization序列化的expr。主要问题是所有班级都有非默认的构造函数和const子女。为了克服此问题,我关注DOC并超载load_construct_datasave_construct_data(最终完成了所有工作(。

我的问题是关于代码中的Mul类。为了分解代码,我开发了一个模板类Op2,该类别用于定义运算符,例如 add Mul(此处仅在此处显示Mul(通过这些类上的CRTP。在Mul::serialize中,我将Expr直接注册为Mul的基类,并完全跳过Op2代码有效,Valgrind很高兴,但是正确吗?还是boost ::序列化需要对完整的类层次结构?

#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/serialization.hpp>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
//forward declaration of my structs
struct Expr;
struct Mul;
struct Int;
//forward declarations of custom boost functions to friend them in the class
namespace b_ser = boost::serialization;
namespace boost {
namespace serialization {
template <class Archive>
void load_construct_data(Archive &ar, Mul *e, const unsigned int);
template <class Archive>
void save_construct_data(Archive &ar, const Mul *a, const unsigned int);
template <class Archive>
void load_construct_data(Archive &ar, Int *e, const unsigned int);
template <class Archive>
void save_construct_data(Archive &ar, const Int *a, const unsigned int);

} // namespace serialization
} // namespace boost
//memory manager
std::vector<std::unique_ptr<Expr>> pool;
// AST
struct Expr {
  virtual ~Expr() {}
  virtual std::vector<Expr const *> children() const = 0;
  virtual std::string identity() const = 0;
  void print(int p) const {
    std::cout << std::setw(p) << ' ';
    std::cout << identity() << "n";
    for (auto a_kid : children()) {
      a_kid->print(p + 2);
    }
  }
  void self_register() const {
    if (std::find_if(pool.begin(), pool.end(), [this](auto const &stored_ptr) {
          return this == stored_ptr.get();
        }) == pool.end()) {
      pool.push_back(std::unique_ptr<Expr>(const_cast<Expr *>(this)));
    }
    for (auto ptr : children()) {
      ptr->self_register();
    }
  }
private:
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive &ar, const unsigned int version) {}
};
struct Int : Expr {
  int const n;
  std::vector<Expr const *> children() const override { return {}; }
  std::string identity() const override {
    return "Int[" + std::to_string(n) + "]@";
  }
  Int(int nn) : n(nn) {}
  template <class Archive>
  void serialize(Archive &ar, const unsigned int version) {
    ar &boost::serialization::base_object<Expr>(*this);
  }
  template <class Archive>
  friend void b_ser::save_construct_data(Archive &ar, const Int *i,
                                         const unsigned int) {
    ar << i->n;
  }
  template <class Archive>
  friend void b_ser::load_construct_data(Archive &ar, Int *i,
                                         const unsigned int) {
    int n;
    ar >> n;
    ::new (i) Int(n);
  }
};
template <class T> struct Op2 : Expr {
  std::vector<Expr const *> children() const override { return {l, r}; }
  std::string identity() const override { return T::message; }
  Op2(Expr const *ll, Expr const *rr) : l(ll), r(rr) {}
protected:
  Expr const *l;
  Expr const *r;
};
struct Mul : Op2<Mul> {
  using Op2::Op2;
  static auto const constexpr message = "Mul";
private:
  friend class boost::serialization::access;
  template <class Archive>
  void serialize(Archive &ar, const unsigned int version) {
    ar &boost::serialization::base_object<Expr>(*this);
  }
  template <class Archive>
  friend void b_ser::save_construct_data(Archive &ar, const Mul *a,
                                         const unsigned int) {
    ar << a->l;
    ar << a->r;
  }
  template <class Archive>
  friend void b_ser::load_construct_data(Archive &ar, Mul *e,
                                         const unsigned int) {
    Expr *l, *r;
    ar >> l;
    ar >> r;
    ::new (e) Mul(l, r);
    e->self_register();
  }
};
template <class T, class... Args> T *store(Args... args) {
  auto to_store = std::make_unique<T>(std::forward<Args>(args)...);
  auto raw_ptr = to_store.get();
  pool.push_back(std::move(to_store));
  return raw_ptr;
}
BOOST_CLASS_EXPORT(Expr)
BOOST_CLASS_EXPORT(Int)
BOOST_CLASS_EXPORT(Mul)
int main(int argc, char *argv[]) {
  {
    auto deux = store<Int>(2);
    auto trois = store<Int>(3);
    auto m_23 = store<Mul>(trois, deux);
    auto quatre = store<Int>(4);
    auto root = store<Mul>(m_23, quatre);
    Expr *e_root = root;
    root->print(2);
    std::ofstream of("arxiv");
    boost::archive::text_oarchive oa(of);
    oa << e_root;
  }
  std::cout << "==================="
            << "n";
  {
    std::ifstream isf("arxiv");
    boost::archive::text_iarchive is(isf);
    Expr *expr;
    is >> expr;
    expr->print(2);
  }
  return 0;
}

首先,我认为您必须使用MSVC,因为您的代码不是有效的标准C 。

修复事物表明它似乎在Clang和GCC上正常工作。

启用地址齐射器迅速揭示了我/相信/可能起源于内部内部的单例代码的错误。我暂时会忽略它。

由于这些错误,我很长时间看了这件事,试图查看该代码是否应该归咎于代码。

这样做的时候,我发现很多事情都可以变得更简单。

  • 如果您只添加 private 默认构造函数,则可以在不构造data的情况下进行。

    关于您的课程的重要一件事是不变的,很容易证明不变性是通过这种方式进行的。

  • 您无需每个二进制操作员的子类,因为它们实际上没有行为。考虑提供 message频段范围内

  • 而不是制作vector<unique_ptr>,您可以使用隐式拥有其元素的指针容器。这使得查找等效指针a lot 更容易:

    namespace memory_management {
        struct address_less {
            bool operator()(Expr const& a, Expr const& b) const { return &a < &b; }
        };
        static boost::ptr_set<Expr, address_less> pool;
    }
    void Expr::self_register() { memory_management::pool.insert(this); }
    

在所有情况下,所有内容都短得多:

// AST
struct Expr {
    virtual ~Expr() = default;
    virtual std::vector<Expr const *> children() const { return {}; }
    virtual std::string identity() const = 0;
    void print(int p) const {
        std::cout << std::setw(p) << ' ';
        std::cout << identity() << "n";
        for (auto a_kid : children()) {
            a_kid->print(p + 2);
        }
    }
  protected:
    Expr() { self_register(); }
  private:
    void self_register();
    friend class boost::serialization::access;
    template <class Archive> void serialize(Archive &, unsigned) {}
};
namespace memory_management {
    struct address_less {
        bool operator()(Expr const& a, Expr const& b) const { return &a < &b; }
    };
    static boost::ptr_set<Expr, address_less> pool;
}
void ::Expr::self_register() { memory_management::pool.insert(this); }
struct Int : Expr {
    std::string identity() const override { return "Int[" + std::to_string(n) + "]@"; }
    Int(int nn) : n(nn) {}
  private:
    int const n = 0;
    friend class boost::serialization::access;
    Int() = default;
    template <class Archive> void serialize(Archive &ar, unsigned) {
        ar & boost::serialization::base_object<Expr>(*this) 
           & const_cast<int&>(n);
    }
};
namespace Tags {
    struct Mul;
    struct Div;
    struct Plus;
    struct Minus;
    template <typename T> constexpr char const* const message        = "Unknown";
    template <>           constexpr char const* const message<Mul>   = "Mul";
    template <>           constexpr char const* const message<Div>   = "Div";
    template <>           constexpr char const* const message<Plus>  = "Plus";
    template <>           constexpr char const* const message<Minus> = "Minus";
}
template <class T> struct Op2 : Expr {
    std::vector<Expr const *> children() const override { return { l, r }; }
    std::string identity() const override { return Tags::message<T>; }
    Op2(Expr *ll, Expr *rr) : l(ll), r(rr) {}
  protected:
    friend class boost::serialization::access;
    Op2() = default;
    Expr *l = nullptr;
    Expr *r = nullptr;
    template <class Archive> void serialize(Archive &ar, unsigned) {
        ar & boost::serialization::base_object<Expr>(*this)
           & l & r;
    }
};
using Mul   = Op2<Tags::Mul>;
using Div   = Op2<Tags::Div>;
using Plus  = Op2<Tags::Plus>;
using Minus = Op2<Tags::Minus>;

奖金:AST建筑物的DSL

我认为能够说:

会更好
Expr const* root((as_expr(3) * 2) + 5 + (as_expr(7) / 25));

所以,让我们这样做:

namespace builder {
    struct Atom {
        Atom(Expr* expr) : expr(expr)  {}
        Atom(int i) : expr(new Int(i)) {}
        Expr* expr;
        explicit operator Expr const*() const { return expr; }
    };
    template <typename T>
    Atom as_expr(T&& v) { return std::forward<T>(v); }
    Atom operator+(Atom a, Atom b) { return new Plus(a.expr, b.expr); }
    Atom operator-(Atom a, Atom b) { return new Minus(a.expr, b.expr); }
    Atom operator*(Atom a, Atom b) { return new Mul(a.expr, b.expr); }
    Atom operator/(Atom a, Atom b) { return new Div(a.expr, b.expr); }
}

实时演示

活在coliru

#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/ptr_container/ptr_set.hpp>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>
// AST
struct Expr {
    virtual ~Expr() = default;
    virtual std::vector<Expr const *> children() const { return {}; }
    virtual std::string identity() const = 0;
    void print(int p) const {
        std::cout << std::setw(p) << ' ';
        std::cout << identity() << "n";
        for (auto a_kid : children()) {
            a_kid->print(p + 2);
        }
    }
  protected:
    Expr() { self_register(); }
  private:
    void self_register();
    friend class boost::serialization::access;
    template <class Archive> void serialize(Archive &, unsigned) {}
};
namespace memory_management {
    struct address_less {
        bool operator()(Expr const& a, Expr const& b) const { return &a < &b; }
    };
    static boost::ptr_set<Expr, address_less> pool;
}
void ::Expr::self_register() { memory_management::pool.insert(this); }
struct Int : Expr {
    std::string identity() const override { return "Int[" + std::to_string(n) + "]@"; }
    Int(int nn) : n(nn) {}
  private:
    int const n = 0;
    friend class boost::serialization::access;
    Int() = default;
    template <class Archive> void serialize(Archive &ar, unsigned) {
        ar & boost::serialization::base_object<Expr>(*this) 
           & const_cast<int&>(n);
    }
};
namespace Tags {
    struct Mul;
    struct Div;
    struct Plus;
    struct Minus;
    template <typename T> constexpr char const* const message        = "Unknown";
    template <>           constexpr char const* const message<Mul>   = "Mul";
    template <>           constexpr char const* const message<Div>   = "Div";
    template <>           constexpr char const* const message<Plus>  = "Plus";
    template <>           constexpr char const* const message<Minus> = "Minus";
}
template <class T> struct Op2 : Expr {
    std::vector<Expr const *> children() const override { return { l, r }; }
    std::string identity() const override { return Tags::message<T>; }
    Op2(Expr *ll, Expr *rr) : l(ll), r(rr) {}
  protected:
    friend class boost::serialization::access;
    Op2() = default;
    Expr *l = nullptr;
    Expr *r = nullptr;
    template <class Archive> void serialize(Archive &ar, unsigned) {
        ar & boost::serialization::base_object<Expr>(*this)
           & l & r;
    }
};
using Mul   = Op2<Tags::Mul>;
using Div   = Op2<Tags::Div>;
using Plus  = Op2<Tags::Plus>;
using Minus = Op2<Tags::Minus>;
namespace builder {
    struct Atom {
        Atom(Expr* expr) :expr(expr){}
        Atom(int i) :expr(new Int(i)){}
        Expr* expr;
        explicit operator Expr const*() const { return expr; }
    };
    template <typename T>
    Atom as_expr(T&& v) { return std::forward<T>(v); }
    Atom operator+(Atom a, Atom b) { return new Plus(a.expr, b.expr); }
    Atom operator-(Atom a, Atom b) { return new Minus(a.expr, b.expr); }
    Atom operator*(Atom a, Atom b) { return new Mul(a.expr, b.expr); }
    Atom operator/(Atom a, Atom b) { return new Div(a.expr, b.expr); }
}
BOOST_CLASS_EXPORT(Expr)
BOOST_CLASS_EXPORT(Int)
BOOST_CLASS_EXPORT(Mul)
BOOST_CLASS_EXPORT(Div)
BOOST_CLASS_EXPORT(Plus)
BOOST_CLASS_EXPORT(Minus)
int main() {
    std::cout << std::unitbuf;
    {
        using builder::as_expr;
        Expr const* root((as_expr(3) * 2) + 5 + (as_expr(7) / 25));
        root->print(2);
        std::ofstream of("arxiv");
        boost::archive::text_oarchive oa(of);
        oa << root;
    }
    std::cout << "===================n";
    {
        std::ifstream isf("arxiv");
        boost::archive::text_iarchive is(isf);
        Expr *expr = nullptr;
        is >> expr;
        expr->print(2);
    }
    memory_management::pool.clear(); // no memory leaks
}

打印

  Plus
    Plus
      Mul
        Int[3]@
        Int[2]@
      Int[5]@
    Div
      Int[7]@
      Int[25]@
===================
  Plus
    Plus
      Mul
        Int[3]@
        Int[2]@
      Int[5]@
    Div
      Int[7]@
      Int[25]@