使用boost :: serialization跳过层次结构继承中的中间类

时间:2018-02-10 13:22:20

标签: c++ c++11 boost boost-serialization

上下文:我有一个树状结构,代表我想要使用boost::serialization序列化的Expr的AST。主要问题是所有类都有非默认构造函数和const子函数。为了解决这个问题,我遵循了文档并重载了load_construct_datasave_construct_data(最终完成了所有工作)。

我的问题是关于代码中的Mul类。为了对代码进行分解,我开发了一个模板类Op2,用于通过CRTP定义运算符,例如添加Mul(此处仅显示Mul)这些课程。在Mul::serialize中,我直接将Expr注册为Mul的基类,并完全跳过Op2代码有效,valgrind很高兴,但这是正确的吗?或者boost :: serialization是否需要整个类层次结构?

#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;
}

1 个答案:

答案 0 :(得分:0)

首先,我结束了你must be using MSVC,因为你的代码不是有效的标准C ++。

修复内容显示它似乎在ClangGCC上正常工作。

  

启用AddressSanitizer可以快速显示我/相信/可能源自Boost Serialization内部单例代码的错误。我现在暂时忽略它。

由于这些错误,我看了很长时间,试图查看代码是否应该受到责备。

在这样做的同时,我发现许多事情可以变得更加简单。

  • 如果您只是添加私有默认构造函数以进行序列化,则可以不使用构造数据。

    关于类的重要之处在于不变量,并且很容易证明不变量是以这种方式保持反序列化的。

  • 你可以不使用每个二元运算符的子类,因为它们实际上没有添加任何行为。考虑提供message常数带外

  • 而不是制作vector<unique_ptr>,您可以使用隐式拥有其元素的指针容器。这使得例如查找等效指针 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); }
}

现场演示

<强> Live On 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]@