提升引用成员抽象类的序列化

Posted

技术标签:

【中文标题】提升引用成员抽象类的序列化【英文标题】:Boost serialization of reference member abstract class 【发布时间】:2015-09-04 22:26:39 【问题描述】:

我试图弄清楚如何序列化我与 Boost 放在一起的类。我会直接进入代码:

#ifndef TEST_H_
#define TEST_H_

#include <iostream>
#include <boost/serialization/serialization.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>

class Parent

public:
        int test_val = 1234234;
        int p()
        
                return 13294;
        
        int get_test_val()
        
                std::cout << test_val << std::endl;
                return test_val;
        
        friend class boost::serialization::access;
        template<class Archive>
            void serialize(Archive &ar, const unsigned int /*version*/)
        
                ar &test_val;
        
;

class RefMem : public Parent

public: 
        RefMem()
        
                test_val = 12342;
                std::cout << test_val << std::endl;
        
;


class Test

public:
        friend class boost::serialization::access;
        int t_;
        Parent &parent_;
        Test(int t, Parent &&parent = RefMem());
        template<class Archive>
        void serialize(Archive &ar, const unsigned int file_version)
                ar &t_;
                ar &parent_;
        
        //template<class
;


#endif

#include "test.h"
#include <iostream>
#include <sstream>
#include <boost/serialization/serialization.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>

Test :: Test(int t, Parent &&parent) : parent_(parent)

        std::cout << this->parent_.test_val << std::endl;
        t_ = t;
        parent_ = parent;


    int main()
    
            Test test = Test(50);
            std::cout << "t_: " << test.t_ << std::endl;
            std::cout << "Test val: " << test.parent_.get_test_val() << std::endl;
            std::ostringstream oss;
            
                    boost::archive::text_oarchive oa(oss);
                    oa << test;
            

            Test cloned;
            std::istringstream iss(oss.str());
                   
                    boost::archive::text_iarchive ia(iss);
                    ia >> cloned;
                


            std::cout << "t_: " << cloned.t_ << std::endl;
            std::cout << "Test val: " << cloned.parent_.get_test_val() << std::endl;
    

我基本上是在黑暗中拍摄。我是 C++ 的新手,我可以得到一个基本示例来工作,但没有像这样我序列化作为抽象类的子级的引用成员然后反序列化它的情况。这段代码只是复制我在另一个程序中尝试做的事情。我有一些随机函数/变量仅用于测试。

编辑:如何让这段代码编译并正常工作?

【问题讨论】:

【参考方案1】:

您对引用的所有权语义感到困惑。

    引用parent_ 只是“指向”RefMem¹ 的一个实例。当您序列化时,编写这些“容易”(因为它们是左值引用,值本身将被序列化)。

    但是对于反序列化,事情就没有那么简单了,仅仅是因为我们没有MemRef 的实例来“指向”。我们可以期望 Boost 序列化(不知何故)凭空动态实例化 MemRef 并默默地为它提供参考点。但是,这充其量只会导致内存泄漏。

    关于引用成员还有另一件事。引用成员只能在构造函数的初始化列表中初始化。

    因为 Boost Serialization 序列化 它不构造这些对象,问题是如何甚至可以初始化引用。

    您当前的构造函数存在许多相关问题:

    Test(int t, Parent && parent = RefMem()) : parent_(parent) 
        std::cout << __FUNCTION__ << ":" << this->parent_.test_val << "\n";
        t_      = t;
        parent_ = parent; // OOPS! TODO FIXME
    
    
    首先,构造函数禁用了编译器生成的默认构造函数,因此Test cloned; 行甚至无法编译 其次,parent 的默认参数是一个右值引用,一旦构造函数返回,它就会变得悬空。你的程序有Undefined Behaviour

    第三行

    parent_ = parent; // OOPS! TODO FIXME
    

    没有做你认为它做的事。它从parent 复制Parent 对象的覆盖parent_ 引用的对象。这可能不可见,因为parent_parent 在这里是同一个对象,但甚至涉及到对象切片 (What is object slicing?)。


做什么?

最好重新组合并点击文档for Serialization of References:

包含引用成员的类通常需要非默认值 构造函数作为引用只能在构造实例时设置。 上一节的例子稍微复杂一些,如果这个类有 参考成员。 这提出了对象如何以及在何处的问题 被引用的存储以及它们是如何创建的。 还有 关于对多态基类的引用的问题。基本上,这些是 关于指针出现的相同问题。这并不奇怪,因为 引用确实是一种特殊的指针。

我们通过序列化引用来解决这些问题,就好像它们是 指针。

(强调我的)

文档确实继续建议load_construct_data/save_construct_data 以减轻Test 的非默认可构造性。

请注意,他们将引用成员作为指针处理的建议看起来不错,但只有如果实际指向的对象也通过指针序列化才有意义相同的存档。在这种情况下,Object Tracking 将发现别名指针并避免创建重复实例。

如果没有,您仍然会出现内存泄漏,并且可能会破坏程序状态。

演示使用load/save_construct_data

这里是上面概述的技术的演示。请注意,我们正在泄漏动态分配的对象。我不喜欢这种风格,因为它本质上将引用视为指针。

如果这是我们想要的,我们应该考虑使用指针(见下文)

Live On Coliru

#ifndef TEST_H_
#define TEST_H_

#include <iostream>
#include <boost/serialization/serialization.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>

class Parent 
  public:
    int test_val = 1234234;

    int p()  return 13294; 

    int get_test_val() 
        std::cout << __PRETTY_FUNCTION__ << ":" << test_val << "\n";
        return test_val;
    

    template <class Archive> void serialize(Archive &ar, unsigned) 
        ar & test_val; 
    
;

class RefMem : public Parent 
  public:
    RefMem() 
        test_val = 12342;
        std::cout << __PRETTY_FUNCTION__ << ":" << test_val << "\n";
    
;

class Test 
  public:
    friend class boost::serialization::access;
    int t_;
    Parent &parent_;

    Test(int t, Parent& parent) : parent_(parent) 
        std::cout << __PRETTY_FUNCTION__ << ":" << this->parent_.test_val << "\n";
        t_      = t;
    

    template <class Archive> void serialize(Archive &ar, const unsigned int file_version) 
        ar &t_;
        //ar &parent_; // how would this behave? We don't own it... Use pointers
    
    // template<class
;

namespace boost  namespace serialization 
    template<class Archive>
        inline void save_construct_data(Archive & ar, const Test * t, const unsigned int file_version) 
            // save data required to construct instance
            ar << t->t_;
            // serialize reference to Parent as a pointer
            Parent* pparent = &t->parent_;
            ar << pparent;
        

    template<class Archive>
        inline void load_construct_data(Archive & ar, Test * t, const unsigned int file_version) 
            // retrieve data from archive required to construct new instance
            int m;
            ar >> m;
            // create and load data through pointer to Parent
            // tracking handles issues of duplicates.
            Parent * pparent;
            ar >> pparent;
            // invoke inplace constructor to initialize instance of Test
            ::new(t)Test(m, *pparent);
        


#endif

#include <iostream>
#include <sstream>
#include <boost/serialization/serialization.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>

int main() 
    Parent* the_instance = new RefMem;

    Test test = Test(50, *the_instance);

    std::cout << "t_: " << test.t_ << "\n";
    std::cout << "Test val: " << test.parent_.get_test_val() << "\n";
    std::ostringstream oss;
    
        boost::archive::text_oarchive oa(oss);
        Test* p = &test;
        oa << the_instance << p; // NOTE SERIALIZE test AS-IF A POINTER
    

    
        Parent* the_cloned_instance = nullptr;
        Test* cloned = nullptr;

        std::istringstream iss(oss.str());
        
            boost::archive::text_iarchive ia(iss);
            ia >> the_cloned_instance >> cloned;
        

        std::cout << "t_: " << cloned->t_ << "\n";
        std::cout << "Test val: " << cloned->parent_.get_test_val() << "\n";
        std::cout << "Are Parent objects aliasing: " << std::boolalpha << 
            (&cloned->parent_ == the_cloned_instance) << "\n";
    

打印

RefMem::RefMem():12342
Test::Test(int, Parent&):12342
t_: 50
int Parent::get_test_val():12342
Test val: 12342
Test::Test(int, Parent&):12342
t_: 50
int Parent::get_test_val():12342
Test val: 12342
Are Parent objects aliasing: true

或者:说我们想要的

为了避免与引用成员相关的泄漏和可用性问题,让我们使用 shared_ptr 代替!

Live On Coliru

#include <iostream>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/make_shared.hpp>

class Parent 
  public:
    int test_val = 1234234;

    int p()  return 13294; 

    int get_test_val() 
        std::cout << __PRETTY_FUNCTION__ << ":" << test_val << "\n";
        return test_val;
    

    template <class Archive> void serialize(Archive &ar, unsigned) 
        ar & test_val; 
    
;

class RefMem : public Parent 
  public:
    RefMem() 
        test_val = 12342;
        std::cout << __PRETTY_FUNCTION__ << ":" << test_val << "\n";
    
;

using ParentRef = boost::shared_ptr<Parent>;

class Test 
  public:
    int t_ = 0;
    ParentRef parent_;

    Test() = default;
    Test(int t, ParentRef parent) : t_(t), parent_(parent)  

    template <class Archive> void serialize(Archive &ar, const unsigned int file_version) 
        ar & t_ & parent_;
    
;

#include <sstream>

int main() 
    ParentRef the_instance = boost::make_shared<RefMem>();

    Test test = Test(50, the_instance);

    std::cout << "t_: " << test.t_ << "\n";
    std::cout << "Test val: " << test.parent_->get_test_val() << "\n";
    std::ostringstream oss;
    
        boost::archive::text_oarchive oa(oss);
        oa << the_instance << test; // NOTE SERIALIZE test AS-IF A POINTER
    

    
        ParentRef the_cloned_instance;
        Test cloned;

        std::istringstream iss(oss.str());
        
            boost::archive::text_iarchive ia(iss);
            ia >> the_cloned_instance >> cloned;
        

        std::cout << "t_: " << cloned.t_ << "\n";
        std::cout << "Test val: " << cloned.parent_->get_test_val() << "\n";
        std::cout << "Are Parent objects aliasing: " << std::boolalpha << 
            (cloned.parent_ == the_cloned_instance) << "\n";
    

请注意,不再有任何复杂性。没有内存泄漏,即使您没有单独序列化 RefMem 实例。并且对象跟踪可以很好地使用共享指针(通过boost/serialization/shared_pointer.hpp 实现)。


¹ 或其他任何源自 Parent 的东西,显然

【讨论】:

添加了两个实时样本along the lines of the documentation suggestions 和一个more sane approach,避免了所有的陷阱。 很好的答案。我得读几遍。我使用双与号的原因之一是来自这里的建议:***.com/questions/32211321/… ... 是否有一种干净(无泄漏)的方法可以根据您在此处所做的将默认值的功能获取到抽象参数?我注意到你漏掉了。 关于Parent&amp;&amp;:好点子。我评论了他的回答,他在那里犯了一个错误。 关于默认值:我没有忽略它。没有抽象参数之类的东西。 (您可能是指对多态基础的引用)。在我的“理智方法”中,默认值只是一个空的 shared_pointer,但请随意给它一个默认值,因为智能指针 清理。给你Live On Coliru 这非常适合谷歌搜索。 SO 有多个答案,文档也有:boost.org/doc/libs/1_46_1/libs/serialization/doc/…

以上是关于提升引用成员抽象类的序列化的主要内容,如果未能解决你的问题,请参考以下文章

C#-Json-抽象类的反序列化

尽管结构已序列化,但提升错误“结构没有名为'序列化'的成员”

没有数据成员的派生类的引用提升

如何在抽象类的成员函数中使用/转换对“this”的引用?

克隆的使用

如何提升::序列化指针和引用?