Serializing generic C++ objects is a complex task in and of itself. This section describes some of the more advanced features of RCF's internal serialization framework.
RCF will automatically detect and serialize polymorphic pointers and references, as fully derived types. However, to do this, RCF needs to be configured with two pieces of information about the polymorphic type being serialized. First, RCF needs a runtime identifier string for the derived type. Second, it needs to know which base types the derived type will be serialized through.
Here is an example of a polymorphic class hierarchy, with associated serialization functions:
Note that SF::serializeParent()
is used to invoke base class serialization code. If you try to serialize the parent class directly, e.g. by calling ar & static_cast<A&>(*this)
, RCF will detect that the parent class is actually a derived class, and will try to serialize the derived class once again.
Now consider the following RCF interface, which utilizes a class X
containing polymorphic A
pointers:
In order to polymorphically serialize X::mAPtr
, we need to do two things:
SF::registerType()
to set runtime identifiers for the B
and C
classes. The runtime identifiers will be included in serialized archives when B
and C
objects are serialized, and will allow the deserialization code to construct objects of the appropriate type.SF::registerBaseAndDerived()
to specify which base classes the derived classes will be serialized through.In our case, the following code is sufficient:
This code needs to be executed at the startup of both the client and the server. Once executed, we can make remote calls to the Echo()
method and the polymorphic A
pointers will be serialized and deserialized, as fully derived types.
If you serialize a pointer to the same object twice, RCF will by default serialize the entire object twice. This means that when the pointers are deserialized, they will point to two distinct objects. In most applications this is usually not an issue. However, some applications may want the deserialization code to instead create two pointers to the same object.
RCF supports this through a pointer tracking concept, where an object is serialized in its entirety, only once, regardless of how many pointers to it are serialized. Upon deserialization, only a single object is created, and multiple pointers can then be deserialized, pointing to the same object.
To demonstrate pointer tracking, here is an an I_Echo
interface with an Echo()
function that takes a pair of std::shared_ptr<>
objects:
Here is client-side code to call Echo()
:
If we make a call to Echo()
with a pair of shared_ptr<>
objects pointing to the same std::string
, we'll find that the returned pair points to two distinct std::string
objects. To get them to point to the same std::string
, we can enable pointer tracking, on the client-side:
, and also on the server-side:
The two returned shared_ptr<>
objects will now point to the same instance.
Pointer tracking is a relatively expensive feature and should only be enabled if your application requires it. Pointer tracking requires the serialization framework to track all pointers and values that are being serialized to and from an archive, resulting in significant performance overhead.
Serialization and deserialization tend to be performed symmetrically, in the sense that serialization code writes a type T
to an archive, and deserialization code reads the same type T
from the archive.
Symmetry is not a requirement for serialization, however. If two classes A
and B
have serialization functions that serialize the same number and type of members, then you can serialize an A
instance to an archive and deserialize a B
instance from the archive. Essentially, the classes A
and B
are interchangeable from a serialization point of view.
RCF guarantees serialization interchangeability for a number of types. For example, for any type T
, T
and T *
are interchangeable. So you can serialize a pointer, and then deserialize it as a value:
Furthermore, smart pointers are interchangeable with native pointers, so you can serialize a std::shared_ptr<T>
and then deserialize it into a value T
. Here is another example:
A significant consequence of this is that if you start out using T
, T*
, std::shared_ptr<T>
, std::unique_ptr<T>
or some other smart pointer in your RCF interface, you can always change it at a later point in time, without breaking backward compatibility.
The following are classes of types that RCF guarantees to be interchangeable:
T
, T *
, std::unique_ptr<T>
, std::shared_ptr<T>
, boost::scoped_ptr<T>
, boost::shared_ptr<T>
unsigned int
and int
T
, where T
is non-primitiveT
(except std::vector<>
and std::basic_string<>
), where T
is primitivestd::vector<T>
and std::basic_string<T>
, where T
is primitivestd::string
, std::vector<char>
, and RCF::ByteBuffer
Serialization of std::wstring
objects presents some portability issues, as different platforms have different definitions of wchar_t
and different Unicode encodings for std::wstring
.
RCF resolves this by converting std::wstring
objects to UTF-8 before serializing.
wchar_t
, RCF assumes that any std::wstring
passed to it, is encoded in UTF-16, and converts between UTF-16 and UTF-8.wchar_t
, RCF assumes that any std::wstring
passed to it is encoded in UTF-32, and converts between UTF-32 and UTF-8.If your application is sending or receiving std::wstring
objects, encoded in something other than the assumed UTF-16 or UTF-32 encodings, you should be aware that cross-platform portability may be affected.