提升引用成员摘要的序列化 class

Boost serialization of reference member abstract class

我正在尝试弄清楚如何序列化我与 Boost 放在一起的 class。我将直接获取代码:

#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++ 的新手,我可以得到一个基本的例子来工作,但没有像这样我序列化一个引用成员,它是一个抽象 class 的 child,然后反序列化它。这段代码只是复制了我在另一个程序中尝试做的事情。我有一些随机 functions/variables 只是为了测试。

编辑:我怎样才能让这段代码正常编译和工作?

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

  1. 引用 parent_ 只是 "points" 到 RefMem¹ 的一个实例。当你序列化时,写这些是"easy"(因为它们是左值引用,值本身将被序列化)。

    然而对于反序列化,事情就没那么简单了,只是因为我们没有MemRef到"point"的实例。我们可以期望 Boost Serialization(以某种方式)凭空动态实例化一个 MemRef 并默默地使引用指向它。然而,充其量这将导致内存泄漏。

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

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

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

    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 的默认参数是右值引用,它在构造函数 returns 后立即变为悬空。您的程序有 Undefined Behaviour
    • 第三行

      parent_ = parent; // OOPS! TODO FIXME
      

      并不像您认为的那样。它从 parent 复制 Parent 对象的值 parent_ 引用的对象。这可能不可见,因为 parent_parent 在这里是同一个对象,但甚至涉及对象切片 (What is object slicing?).


做什么?

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

Classes that contain reference members will generally require non-default constructors as references can only be set when an instance is constructed. The example of the previous section is slightly more complex if the class has reference members. This raises the question of how and where the objects being referred to are stored and how are they created. Also there is the question about references to polymorphic base classes. Basically, these are the same questions that arise regarding pointers. This is no surprise as references are really a special kind of pointer.

We address these questions by serializing references as though they were pointers.

(强调我的)

文档继续建议 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 派生的任何其他内容,显然