c++ - 引用成员抽象类的Boost序列化

标签 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. 引用文献 parent_仅仅“指向”RefMem 的实例¹.当你序列化时,写这些很“容易”(因为它们是左值引用,值本身将被序列化)。

    然而对于反序列化,事情就没那么简单了,因为我们没有MemRef的实例。 “指向”到。我们可以期望 Boost 序列化(以某种方式)动态实例化一个 MemRef无中生有,默默地给它做引用点。然而,充其量这会导致内存泄漏。

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

    因为 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 :

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 派生的任何其他内容,显然

关于c++ - 引用成员抽象类的Boost序列化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32407426/

相关文章:

c++ - 交联 vector 中的段错误

c++ - 如何将 Eclipse C++ 项目移植到 Visual Studio?

C++ 帕斯卡三角形

c++ - 如何在阻塞调用周围实现定时等待?

c++ - 如何将一组 C++ 动态分配的对象表示为 BGL(Boost Graph Library)图以获得它们的依赖图?

c++ - 使用模板函数打印智能指针 vector

c++ - 在辅助类中访问 boost 矩阵的子片

c++ - Boost类序列化,成员类型的改变

c++ - 使用 boost 反序列化时输入流错误

c++ - Boost序列化不适用于shared_ptr <int>