使用boost::serialization跳过继承层级中间类的AST序列化问题
Great question—handling template-based binary operators like Mul (via your Op2 base template) with boost::serialization when you've got no default constructors and const members is totally doable, and you're already on the right track using load_construct_data and save_construct_data. Let's walk through how to implement this cleanly so you can reuse the Op2 serialization logic across all your binary operators (Add, Mul, etc.).
First: Set Up Your Core AST Classes
Let's start with a minimal version of your AST structure to ground the example:
#include <boost/serialization/serialization.hpp> #include <boost/serialization/unique_ptr.hpp> #include <boost/serialization/export.hpp> #include <memory> // Abstract base class for all expressions class Expr { public: virtual ~Expr() = default; friend class boost::serialization::access; template<class Archive> void serialize(Archive& ar, const unsigned int version) { // No base members to serialize here, but this is required for polymorphism } }; // Template base class for binary operators (Add, Mul, etc.) template<typename Derived> class Op2 : public Expr { public: const std::unique_ptr<Expr> lhs; const std::unique_ptr<Expr> rhs; // No default constructor—only this parameterized one Op2(std::unique_ptr<Expr> left, std::unique_ptr<Expr> right) : lhs(std::move(left)), rhs(std::move(right)) {} friend class boost::serialization::access; template<class Archive> void serialize(Archive& ar, const unsigned int version) { // Serialize the base Expr class first ar & boost::serialization::base_object<Expr>(*this); // Don't serialize lhs/rhs here—they're const, so we handle them in construct_data functions } }; // Concrete Mul operator class, inheriting from Op2 class Mul : public Op2<Mul> { public: // Inherit the Op2 constructor to avoid repeating code using Op2<Mul>::Op2; }; // Assume a Literal class for leaf nodes (e.g., numbers) class Literal : public Expr { public: const int value; Literal(int val) : value(val) {} friend class boost::serialization::access; template<class Archive> void serialize(Archive& ar, const unsigned int version) { ar & boost::serialization::base_object<Expr>(*this); ar & value; } };
Step 1: Implement save_construct_data for Op2
This function tells boost how to save the data needed to reconstruct an Op2 instance later. Since our Op2 requires two Expr pointers to construct, we'll serialize those:
template<class Archive, typename Derived> void save_construct_data( Archive& ar, const Op2<Derived>* node, const unsigned int version ) { // Save the left and right operands (boost handles unique_ptr serialization automatically) ar & node->lhs; ar & node->rhs; }
Step 2: Implement load_construct_data for Op2
This function reads the saved data and constructs the Op2 (or its derived class like Mul) using placement new (since we can't use a default constructor for const members):
template<class Archive, typename Derived> void load_construct_data( Archive& ar, Op2<Derived>* node, const unsigned int version ) { // First read the operands we saved std::unique_ptr<Expr> lhs, rhs; ar & lhs; ar & rhs; // Use placement new to construct the Op2<Derived> instance in the provided memory ::new(node) Op2<Derived>(std::move(lhs), std::move(rhs)); }
Step 3: Register Polymorphic Types
Since we're using polymorphism (Expr* pointing to Mul or Literal), we need to tell boost about our concrete classes. Add these lines after defining each derived class:
BOOST_CLASS_EXPORT(Mul) BOOST_CLASS_EXPORT(Literal) // If Expr is abstract, add this to avoid serialization errors for the base class BOOST_SERIALIZATION_ASSUME_ABSTRACT(Expr)
Key Pitfalls to Avoid
- Smart Pointer Headers: Always include
<boost/serialization/unique_ptr.hpp>(orshared_ptr.hppif you use those) to let boost serialize smart pointers correctly. - Template Visibility: Keep all serialization functions (including the
save_construct_data/load_construct_datatemplates) in header files, or use explicit instantiation for theDerivedtypes you use (likeMul). - Constructor Consistency: If you change the
Op2constructor signature, you must update thesave_construct_dataandload_construct_datafunctions to match the new parameters. - Const Members: Never try to serialize const members directly in the
serializefunction—they can only be initialized during construction, which is why we rely on theconstruct_datafunctions.
Example Serialization/Deserialization
Here's how you'd use this setup to save and load a Mul expression:
#include <boost/archive/text_oarchive.hpp> #include <boost/archive/text_iarchive.hpp> #include <fstream> int main() { // Create a sample AST: 5 * 3 std::unique_ptr<Expr> expr = std::make_unique<Mul>( std::make_unique<Literal>(5), std::make_unique<Literal>(3) ); // Serialize to a file std::ofstream ofs("ast.txt"); boost::archive::text_oarchive oa(ofs); oa << expr; // Deserialize back std::unique_ptr<Expr> restored_expr; std::ifstream ifs("ast.txt"); boost::archive::text_iarchive ia(ifs); ia >> restored_expr; // The restored_expr now points to a Mul instance identical to the original return 0; }
内容的提问来源于stack exchange,提问作者Davidbrcz

