您对引用的所有权语义感到困惑。
-
引用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
}
做什么?
最好重新组合并点击文档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 的东西,显然