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

时间:2015-09-04 22:26:40

标签: c++ boost boost-serialization

我正在试图弄清楚如何序列化我与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 个答案:

答案 0 :(得分:3)

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

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

    然而,对于反序列化,事情并非如此简单,仅仅因为我们没有MemRef的实例来“指向”。我们可以期待Boost Serialization(以某种方式)凭空动态地实例化MemRef并默默地将参考点指向它。但是,这最多会导致内存泄漏。

  2. 具体来说,参考成员还有另外一件事。引用成员只能在构造函数的初始化列表中初始化。

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

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

    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的默认参数是rvalue-reference,一旦构造函数返回它就会变为悬空。您的计划有Undefined Behaviour
    • 第三行

      parent_ = parent; // OOPS! TODO FIXME
      

      不符合您的想法。它将来自Parent parent对象的复制到 parent_引用的对象上。这可能不可见,因为parent_parent在这里是相同的对象,但是甚至涉及对象切片(What is object slicing?)。

  3. 做什么?

    最好重新组合并点击文档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的内容,显然是