上下文:我有一个树状结构,代表我想要使用boost::serialization
序列化的Expr的AST。主要问题是所有类都有非默认构造函数和const子函数。为了解决这个问题,我遵循了文档并重载了load_construct_data
和save_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;
}
答案 0 :(得分:0)
首先,我结束了你must be using MSVC,因为你的代码不是有效的标准C ++。
启用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>;
我认为能说:
会更好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]@