【问题标题】:C++ boost serialize polymorphism questionC ++ boost序列化多态性问题
【发布时间】:2020-10-27 11:23:23
【问题描述】:

我使用基类的指针来序列化派生类的对象。下面的代码似乎有效。但我对执行的顺序很模糊。输出信息是:
CC 序列化开始
BB 序列化开始
AA 序列化开始
AA 序列化结束
BB序列化结束
CC 序列化结束
CC 序列化开始
BB 序列化开始
AA 序列化开始
AA 序列化结束
BB序列化结束
CC 序列化结束
class_aa
class_bb
class_cc
由于序列化函数不是虚拟的,我想知道为什么CC类中的序列化函数会首先执行,然后是BB,AA的序列化函数。

谢谢

#include <boost/serialization/serialization.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/archive/text_oarchive.hpp> 
#include <boost/archive/text_iarchive.hpp>
#include <iostream>
#include <string>
#include <boost/serialization/export.hpp>
#include <fstream>
using namespace std;

class AA
{
public:
    virtual void foo() = 0;
    AA(string aa) :aa_name(aa) { }
    AA() {}

    template<class Archive>
    void serialize(Archive& ar, unsigned int)
    {
        cout << "AA serialize start" << endl;
        ar& aa_name;
        cout << "AA serialize end" << endl;
    }

    string aa_name;
};
BOOST_SERIALIZATION_ASSUME_ABSTRACT(AA);

class BB : public AA
{
public:
    void foo() {}
    virtual void bar() = 0;
    BB(string aa, string bb) : AA(aa), bb_name(bb) {}
    BB() {}

    template<class Archive>
    void serialize(Archive& ar, unsigned int)
    {
        cout << "BB serialize start" << endl;
        ar& boost::serialization::base_object<AA>(*this);
        ar& bb_name;
        cout << "BB serialize end" << endl;
    }
    string bb_name;
};
BOOST_SERIALIZATION_ASSUME_ABSTRACT(BB);

class CC : public BB
{
public:
    CC(string aa, string bb, string cc) : BB(aa, bb), cc_name(cc) {}
    CC() {}
    void bar() {} 

    template<class Archive>
    void serialize(Archive& ar, unsigned int)
    {
        cout << "CC serialize start" << endl;
        ar& boost::serialization::base_object<BB>(*this);
        ar& cc_name;
        cout << "CC serialize end" << endl;
    }

    string cc_name;
};
BOOST_CLASS_EXPORT(CC)


int main(int, char const**)
{
    AA* obj = new CC("class_aa", "class_bb", "class_cc");
    ofstream outfile("archive_test.txt");
    boost::archive::text_oarchive out_archive(outfile);
    out_archive << obj;
    outfile.close();

    ifstream infile("archive_test.txt");
    boost::archive::text_iarchive ia(infile);
    AA* c;
    ia >> c;
    cout << c->aa_name << endl << dynamic_cast<CC*>(c)->bb_name << endl << dynamic_cast<CC*>(c)->cc_name << endl;
    infile.close();
}

【问题讨论】:

    标签: c++ serialization boost polymorphism


    【解决方案1】:

    Q.既然序列化函数不是虚函数,我想知道为什么CC类中的序列化函数会先执行,然后BB,AA的序列化函数。

    原因是在通过多态引用/指针进行序列化时¹存档将包含序列化类型的类型 ID,因此库知道要反序列化的类型。

    您实际上可以在存档中看到这个:

    22 序列化::archive 18 0 0 1 7 派生 1 0 0 0 0 0 0 8 class_aa 8 class_bb 8 class_cc

    您可以使用alternative export macros 来控制如何导出类型。

    请注意,在上面的示例中,层次结构缺少虚拟析构函数并且存在内存泄漏。后者可能很明显,但前者可能不明显²。

    这里是

    Live On Coliru

    #include <boost/archive/text_iarchive.hpp>
    #include <boost/archive/text_oarchive.hpp>
    #include <boost/serialization/export.hpp>
    #include <boost/serialization/unique_ptr.hpp>
    #include <boost/serialization/serialization.hpp>
    #include <fstream>
    #include <iostream>
    #include <string>
    #include <utility>
    
    class Base {
      public:
        Base(std::string aa) : aa_name(std::move(aa)) {}
        Base() = default;
    
        virtual void foo() const = 0;
        virtual ~Base() = default;
    
      private:
        friend class boost::serialization::access;
        template <class Archive> void serialize(Archive& ar, unsigned int) {
            std::cout << "Base serialize start" << std::endl;
            ar& aa_name;
            std::cout << "Base serialize end" << std::endl;
        }
    
        std::string aa_name;
      protected:
        void qux() const { std::cout << aa_name << std::endl; }
    };
    
    class Middle : public Base {
      public:
        Middle(std::string aa, std::string Middle) : Base(aa), bb_name(std::move(Middle)) {}
        Middle() = default;
    
        void foo() const override { qux(); std::cout << bb_name << std::endl; }
        virtual void bar() const = 0;
    
      private:
        friend class boost::serialization::access;
        template <class Archive> void serialize(Archive& ar, unsigned int) {
            std::cout << "Middle serialize start" << std::endl;
            ar& boost::serialization::base_object<Base>(*this);
            ar& bb_name;
            std::cout << "Middle serialize end" << std::endl;
        }
        std::string bb_name;
    };
    
    class Derived : public Middle {
      public:
        Derived(std::string aa, std::string bb, std::string cc) : Middle(aa, bb), cc_name(std::move(cc)) {}
        Derived() = default;
    
        void bar() const override { foo(); std::cout << cc_name << std::endl; }
    
     private:
        friend class boost::serialization::access;
        template <class Archive> void serialize(Archive& ar, unsigned int) {
            std::cout << "Derived serialize start" << std::endl;
            ar& boost::serialization::base_object<Middle>(*this);
            ar& cc_name;
            std::cout << "Derived serialize end" << std::endl;
        }
    
        std::string cc_name;
    };
    
    BOOST_SERIALIZATION_ASSUME_ABSTRACT(Base)
    BOOST_SERIALIZATION_ASSUME_ABSTRACT(Middle)
    BOOST_CLASS_EXPORT(Derived)
    
    int main() {
        using Ptr = std::unique_ptr<Base>;
    
        {
            std::ofstream outfile("archive_test.txt");
            boost::archive::text_oarchive out_archive(outfile);
    
            Ptr obj = std::make_unique<Derived>("class_aa", "class_bb", "class_cc");
            out_archive << obj;
        }
    
        {
            std::ifstream infile("archive_test.txt");
            boost::archive::text_iarchive ia(infile);
            Ptr obj;
            ia >> obj;
    
            if (auto cp = dynamic_cast<Derived*>(obj.get()))
                cp->bar();
        }
    }
    

    打印

    Derived serialize start
    Middle serialize start
    Base serialize start
    Base serialize end
    Middle serialize end
    Derived serialize end
    Derived serialize start
    Middle serialize start
    Base serialize start
    Base serialize end
    Middle serialize end
    Derived serialize end
    class_aa
    class_bb
    class_cc
    

    ¹ 引用引出了一个完全不同的主题:object tracking

    ²When to use virtual destructors?

    奖金

    给cmets,一些技术分析:

    BOOST_CLASS_EXPORT(Derived) 扩展为

    namespace boost { namespace serialization {
        template <> struct guid_defined<Derived> : boost::mpl::true_ {};
        template <> inline const char* guid<Derived>() { return "Derived"; }
    } }
    
    namespace boost { namespace archive { namespace detail { namespace extra_detail {
        template <> struct init_guid<Derived> {
            static guid_initializer<Derived> const& g;
        };
        guid_initializer<Derived> const& init_guid<Derived>::g =
            ::boost::serialization::singleton<
            guid_initializer<Derived>>::get_mutable_instance()
            .export_guid();
    } } } }
    

    export_guid() 读作:

    guid_initializer const & export_guid() const {
        BOOST_STATIC_WARNING(boost::is_polymorphic< T >::value);
        // note: exporting an abstract base class will have no effect
        // and cannot be used to instantitiate serialization code
        // (one might be using this in a DLL to instantiate code)
        //BOOST_STATIC_WARNING(! boost::serialization::is_abstract< T >::value);
        export_guid(boost::serialization::is_abstract< T >());
        return *this;
    }
    
    void export_guid(mpl::false_) const {
        // generates the statically-initialized objects whose constructors
        // register the information allowing serialization of T objects
        // through pointers to their base classes.
        instantiate_ptr_serialization((T*)0, 0, adl_tag());
    }
    

    注意它如何清楚地证明它只对多态类有意义。 instantiate_ptr_serialization实际上注册了with all known archives的类型。

    无论您的代码组织如何(动态/共享链接、单独的翻译单元与否),都有大量的模板机制可以让所有内容都实例化(并且只实例化一次)。但最后还是去了register_type

    template<class T>
    const basic_pointer_iserializer *
    register_type(T * = NULL){
        const basic_pointer_iserializer & bpis =
            boost::serialization::singleton<
                pointer_iserializer<Archive, T>
            >::get_const_instance();
        this->This()->register_basic_serializer(bpis.get_basic_serializer());
        return & bpis;
    }
    

    它在哪里注册pointer_iserializer:

    template<class Archive, class T>
    class pointer_iserializer :
        public basic_pointer_iserializer
    {
    private:
        virtual void * heap_allocation() const {
            detail::heap_allocation<T> h;
            T * t = h.get();
            h.release();
            return t;
        }
        virtual const basic_iserializer & get_basic_serializer() const {
            return boost::serialization::singleton<
                iserializer<Archive, T>
            >::get_const_instance();
        }
        BOOST_DLLEXPORT virtual void load_object_ptr(
            basic_iarchive & ar,
            void * x,
            const unsigned int file_version
        ) const BOOST_USED;
    public:
        // this should alway be a singleton so make the constructor protected
        pointer_iserializer();
        ~pointer_iserializer();
    };
    

    这会将实际的新建/删除包含在 heap_allocation 中。为简洁起见,我在这里不包括在内,因为我已经在这里进行了更详细的分析:How does boost::serialization allocate memory when deserializing through a pointer?

    【讨论】:

    • 感谢您的回答,并用更好的代码示例指出我的代码错误。我还是有点糊涂。 boost序列化库在通过多态引用/指针序列化时是否使用type id并做dynamic_cast来执行序列化函数?
    • @LIANGKe 总的来说,我认为尝试理解 boost 库的每一点都不健康。但是,如果您像我一样只是想知道,我added some analysis 这样您就可以找到自己的方式。
    • 感谢您的详细分析和建议。我学到了很多东西。
    【解决方案2】:

    documentation of boost中,解释了当类是多态的(即至少有一个虚函数)时,这种机制会自动执行:

    如果基类是多态的,则将序列化最派生类型的对象(在这种情况下是derived_one 或derived_two)。要序列化哪种类型的对象的问题(几乎)由库自动处理。

    【讨论】:

    • 谢谢。但我想知道它是如何实现的。可以看到,serizalize函数并不是虚函数,而是先执行派生类的serizalize函数。
    猜你喜欢
    • 1970-01-01
    • 2011-05-24
    • 1970-01-01
    • 1970-01-01
    • 2013-07-15
    • 2011-04-05
    • 2020-01-15
    • 2011-03-02
    • 2020-10-15
    相关资源
    最近更新 更多