下面包含的所有代码片段都在 boost::serialization 命名空间内定义。
shared_ptr < T > 在 shared_ptr.hpp
中定义。
shared_ptr 的一般类轮廓如下:
- shared_ptr 包括以下成员:
- T *px;
- shared_count pn;,其中包含指向:
- sp_counted_base_impl<T, …>,它派生自多态抽象类sp_counted_base
序列化过程沿着上述树结构进行。
首次尝试实现 shared_ptr 的序列化只是序列化 shared_ptr 的相关成员。这几乎是微不足道的操作:
template<class Archive, class T>
inline void serialize(
Archive & ar,
shared_ptr<T> & t,
const unsigned int file_version,
int
){
ar & t.px; // save the raw pointer
ar & t.pn; // save the shared reference count
}
So far so good. Now for the serialization of shared_count:
template<class Archive>
inline void save(
Archive & ar,
const boost::detail::shared_count & t,
const unsigned int file_version
){
ar << t.pi_;
}
template<class Archive>
inline void load(
Archive & ar,
boost::detail::shared_count & t,
const unsigned int file_version
){
ar >> t.pi_;
}
这个库的一个重要特点是可以在不更改类或模板的声明或定义的情况下指定类或模板的序列化方式。这被称为非侵入式序列化。
shared count 中的 pi_member 是指向 sp_counted_base_impl 实例的指针。由于这个类没有默认构造函数,因此序列化需要指定以下重载函数:
template<class Archive, class P, class D>
inline void save_construct_data(
Archive & ar,
const boost::detail::sp_counted_base_impl * t,
const unsigned int file_version
){
// variables used for construction
ar << t->ptr;
ar << *t;
}
template
inline void load_construct_data(
Archive & ar,
boost::detail::sp_counted_base_impl * t,
const unsigned int file_version
){
P ptr_;
ar >> ptr_;
// placement new
::new(t)boost::detail::sp_counted_base_impl(ptr_, D());
ar >>; *t;
}
这个语句 ar >> ptr_ 很关键。它用于反序列化之前序列化的相同指针。默认的对象跟踪机制会确保不会创建多于一个对象的实例,同时多次反序列化返回的指针都指向相同的对象。因此,无论创建了多少个与特定对象相对应的 shared_ptr 或 shared_count 实例,它们都将指向同一个对象。
由于 sp_counted_base_impl<P, D> 是从 sp_counted_base 派生出来的,因此需要以下操作:
template<class Archive, class P, class D>
inline void serialize(
Archive & ar,
boost::detail::sp_counted_base_impl<P, D> & t,
const unsigned int file_version,
int
){
ar & boost::serialization::base_object<
boost::detail::sp_counted_base
>(*this);
}
这将反过来需要对其基类进行序列化:
inline void serialize(
Archive & ar,
boost::detail::sp_counted & t,
const unsigned int file_version,
int
){
}
demo_shared_ptr.cpp
#include <iomanip>
#include <iostream>
#include <cstddef> // NULL
#include <fstream>
#include <string>
#include <cstdio> // remove
#include <boost/config.hpp>
#if defined(BOOST_NO_STDC_NAMESPACE)
namespace std{
using ::remove;
}
#endif
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/tmpdir.hpp>
#include <boost/serialization/shared_ptr.hpp>
///
// test shared_ptr serialization
class A
{
private:
friend class boost::serialization::access;
int x;
template<class Archive>
void serialize(Archive & ar, const unsigned int /* file_version */){
ar & x;
}
public:
static int count;
A(){++count;} // default constructor
virtual ~A(){--count;} // default destructor
};
BOOST_SERIALIZATION_SHARED_PTR(A)
// ADDITION BY DT
class B : public A
{
private:
friend class boost::serialization::access;
int x;
template<class Archive>
void serialize(Archive & ar, const unsigned int /* file_version */){
ar & boost::serialization::base_object<A>(*this);
}
public:
static int count;
B() : A() {};
virtual ~B() {};
};
BOOST_SERIALIZATION_SHARED_PTR(B)
/
int A::count = 0;
void display(boost::shared_ptr<A> &spa, boost::shared_ptr<A> &spa1)
{
std::cout << "a = 0x" << std::hex << spa.get() << " ";
if (spa.get()) std::cout << "is a " << typeid(*(spa.get())).name() << "* ";
std::cout << "use count = " << std::dec << spa.use_count() << std::endl;
std::cout << "a1 = 0x" << std::hex << spa1.get() << " ";
if (spa1.get()) std::cout << "is a " << typeid(*(spa1.get())).name() << "* ";
std::cout << "use count = " << std::dec << spa1.use_count() << std::endl;
std::cout << "unique element count = " << A::count << std::endl;
}
int main(int /* argc */, char * /*argv*/[])
{
std::string filename(boost::archive::tmpdir());
filename += "/testfile";
// create a new shared pointer to ta new object of type A
boost::shared_ptr<A> spa(new A);
boost::shared_ptr<A> spa1;
spa1 = spa;
display(spa, spa1);
// serialize it
{
std::ofstream ofs(filename.c_str());
boost::archive::text_oarchive oa(ofs);
oa << spa;
oa << spa1;
}
// reset the shared pointer to NULL
// thereby destroying the object of type A
spa.reset();
spa1.reset();
display(spa, spa1);
// restore state to one equivalent to the original
// creating a new type A object
{
// open the archive
std::ifstream ifs(filename.c_str());
boost::archive::text_iarchive ia(ifs);
// restore the schedule from the archive
ia >> spa;
ia >> spa1;
}
display(spa, spa1);
spa.reset();
spa1.reset();
std::cout << std::endl;
std::cout << std::endl;
std::cout << "New tests" << std::endl;
/
// ADDITION BY DT
// create a new shared pointer to ta new object of type A
spa = boost::shared_ptr<A>(new B);
spa1 = spa;
display(spa, spa1);
// serialize it
{
std::ofstream ofs(filename.c_str());
boost::archive::text_oarchive oa(ofs);
oa.register_type(static_cast<B *>(NULL));
oa << spa;
oa << spa1;
}
// reset the shared pointer to NULL
// thereby destroying the object of type B
spa.reset();
spa1.reset();
display(spa, spa1);
// restore state to one equivalent to the original
// creating a new type B object
{
// open the archive
std::ifstream ifs(filename.c_str());
boost::archive::text_iarchive ia(ifs);
// restore the schedule from the archive
ia.register_type(static_cast<B *>(NULL));
ia >> spa;
ia >> spa1;
}
display(spa, spa1);
///
std::remove(filename.c_str());
// obj of type A gets destroyed
// as smart_ptr goes out of scope
return 0;
}
结果
这表示我们还没有完成。由于默认的对象跟踪,无论有多少 shared 指针指向相同的对象,sp_counted_base_impl<P, D> 只会被创建一次。当然,必须这样做。引用计数从1开始,并且永远不会递增。必须在序列化函数中添加代码来维护正确的引用计数。
对空基类 sp_counted_base 的序列化过程似乎是额外的开销。查看 base_object.hpp 中的代码,可以发现 base_object.hpp 提供了两个功能:
- 调用基类数据的序列化。
- 作为副作用,“注册”了基类/派生类关系,以便在运行时可以在基类和派生类之间进行指针转换。
在这种情况下,我们只需要后者的功能,因此我们可以用以下方式替换基对象的序列化操作:
// register the relationship between each derived class
// its polymorphic base
void_cast_register<
boost::detail::sp_counted_base_impl<P, D>
boost::detail::sp_counted_base,
>();
并且我们不必为 sp_counted_base 包含一个无关紧要的序列化器。
最后,如果我们希望能够在 XML 存档中使用这种序列化方式,我们需要指定名称-值对包装器。
实际上,即使这只是一个开始。在这个实现中没有解决的问题包括:
- weak_ptr 没有被处理。我甚至还没有研究过这个问题。
- 其他可能与 shared_ptr 交互的智能指针根本没有被考虑。为了确保实现是完整和正确的,所
- 有这些问题都应该被解决。
- 异常处理还没有得到详尽考虑。
- 还有待发现的其他问题。
共享指针(shared_ptr)的导出(export)。为了支持在不同编译单元(或不同模块)中正确地序列化和反序列化共享指针,Boost序列化库提供了一些特殊的宏,用于导出共享指针类型。这些宏包括:
BOOST_SHARED_POINTER_EXPORT(T)
//用于导出类型 T 的共享指针。这个宏可以帮助确保在不同的编译单元中正确序列化和反序列化 shared_ptr<T>
BOOST_SHARED_POINTER_EXPORT_GUID(T, K)
//这是 BOOST_SHARED_POINTER_EXPORT 的一种特殊版本,它允许为类型 T 提供一个唯一的全局标识符(GUID)K。这在需要确保跨不同模块的序列化一致性时可能很有用。
这些宏的存在是因为共享指针的完整、正确和异常安全的序列化是一个具有挑战性的任务。共享指针可能涉及到跨不同模块的对象共享和生命周期管理等复杂情况。Boost序列化库的实现提供了一个有用的起点,以帮助解决共享指针的序列化问题,但在实际应用中可能需要更多的工作和注意细节来确保完全正确和安全的序列化。这些宏可以帮助处理其中的一些挑战。