【问题标题】:Skip intermediate classes in hierarchy inheritance with boost::serialization使用 boost::serialization 跳过层次继承中的中间类
【发布时间】:2018-07-21 03:13:28
【问题描述】:

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

我的问题是关于代码中的 Mul 类。为了分解代码,我开发了一个模板类Op2,用于通过这些类上的CRTP 定义操作符,例如AddMul(此处仅显示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;
}

【问题讨论】:

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


    【解决方案1】:

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

    修复问题表明它似乎在 ClangGCC 上正常工作。

    启用 AddressSanitizer 可以快速揭示我/相信/可能源自 Boost 序列化内部的单例代码的错误。我暂时忽略它。

    由于这些错误,我看了很久,想看看代码是否有问题。

    在这样做的过程中,我发现很多事情都可以变得简单得多。

    • 如果您只添加 private 用于序列化目的的默认构造函数,则可以不使用构造数据。

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

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

    • 您可以使用隐式拥有其元素的指针容器,而不是创建vector&lt;unique_ptr&gt;。这使得例如很多更容易查找等效指针:

      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]@
    

    【讨论】:

    • 我没有使用 MSVC,原始代码在 ubuntu 16.04 上启用 g++-7 和 C++14 时编译得很好。对不起,我搞砸了我的原始代码。属性应该是指向非 const 对象的 const 指针(即 T * const ptr)。这就是它们在真实代码中的样子,以及它们应该如何出现在此处。因此,添加默认构造函数无济于事,因为编译器抱怨未初始化 const 成员。子类在这里是为了使代码尽可能接近真实的,但你的例子很好。我会考虑ptr_set。最后,你没有回答原来的问题,但是谢谢 :)
    • 我在每一站都解释了这是怎么发生的。我想如果你坚持让事情复杂化,你可能可以从这里推断出来。我知道支持 const 属性的论点,但它们是有代价的。你的选择
    • 很抱歉,您上一条评论的前两句话我没看懂。
    • 好的,现在回家。让我重申一下,您的代码没有任何问题 - 它可以工作,因为中间体永远不会(反)序列化)。我说的有点含蓄,因为我花了很多时间找出 Asan 消息是否来自您的代码(正如我所说的那样,它不是)。
    • 我碰巧看重简单的代码,我的示例确实在 80% 的长度内实现了原始代码的倍数。事实上,我会进一步简化,这是带有Expr *const 数据成员的代码:live
    猜你喜欢
    • 1970-01-01
    • 2020-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-06
    • 2016-11-03
    相关资源
    最近更新 更多