Pointers

Pointers

The serialization and deserialization processes are driven from the member functions serialize() and deserialize(). Through the usage of the operator &(), the serialize() method is always aware of the type of the nested member it is going to serialize. This works well both instance members and pointers as well but it can't work during deserialization.

 

The deserialize() method can understand the type of the instance members but it can't know the actual class of the instance pointed to by one of its members.

Suppose to have the following:

class Base : public virtual tsl::Serializable {
public:
  Base();
  Base *_pointer;
protected:
  virtual void serialize(tsl::OArchive & out) const;
  virtual void deserialize(tsl::IArchive & in);
}

and

class Derived : public virtual Base {
protected:
  virtual void serialize(tsl::OArchive & out) const;
  virtual void deserialize(tsl::IArchive & in);
};

the method Base::deserialize() - when invoked - can't know if the instance pointed to the member Base::_pointer is actually a Base* or Derived*. Of course this information is saved in the serialized stream data, but it is a string, and since C++ doesn't have any introspection method it is not possible to invoke the default constructor and proceed with the deserialization of the derived instance.

This can be done if the Derived class is 'registered' in a map that basically correlates the (demangled) class name saved in the serialized stream with the proper derived class operator new().

Class registration

TSL provides 2 set of API for class registering:

  • low level API
  • ClassInfo proxy

Low level API

 The tsl::ClassInfo::registry static member provides 2 low-level methods:

template <class T>
tsl::rtx::ClassInfo::registry.add<T>();
template <class T>
tsl::rtx::ClassInfo::registry.rem<T>();

 The add() registers the class T, while the rem() unregisters the same.

ClassInfo Proxy

ClassInfo defines a template Proxy class that wraps the two methods above inside the constructor/destructor object lifecycle, allowing a simpler way to perform the same things: 

template <class T> 
struct Proxy {
  Proxy() {registry().add<T>([] () {return new T;});}
  Proxy(Allocator allocator) {registry().add<T>(allocator);}
  ~Proxy() {registry().rem<T>();}
};

Depending by the way in which we decide to use the tools above, with TSL we have 2 ways to register a class for using its pointers inside a program:

  1. register manually all the classes just before deserializing (possible with low-leve APIs and with Proxies) ;
  2. to define static proxies for each class used in pointer deserialization (the preferred method);

Method 1 - low-level API

In the example below the class ManuallyRegistered is registered directly in the main() function. Note that the deregistration is not mandatory, the library will free the item at program exit automatically. 

#include <iostream>
#include <memory>
#include <string>
#include <tsl/json_archive.h>
#include <tsl/serializable.h>

namespace example_5 {

namespace rtx=tsl::rtx;
  
/**
 * a class usable as a pointer inside other class members
 */
class ManuallyRegistered : public tsl::Serializable {
public:
  explicit ManuallyRegistered(const char *s) : _string(s) {}
  ManuallyRegistered() {}
  bool operator==(const ManuallyRegistered &other) const {return (_string==other._string);}
protected:
  virtual void serialize(tsl::OArchive& out) const;
  virtual void deserialize(tsl::IArchive& in);
private:
  std::string _string;
};

void ManuallyRegistered::serialize(tsl::OArchive& out) const {
  out & _string;
}

void ManuallyRegistered::deserialize(tsl::IArchive& in) {
  in & _string;
}

/**
 * this class contains a member that points to an ManuallyRegistered class instance
 */
class Simple : public tsl::Serializable {
public:
  explicit Simple(ManuallyRegistered *p=nullptr) : _pointer(p) {}
  bool operator==(const Simple &other) const;
protected:
  virtual void serialize(tsl::OArchive& out) const;
  virtual void deserialize(tsl::IArchive& in);
private:
  mutable ManuallyRegistered *_pointer;
};

void Simple::serialize(tsl::OArchive& out) const {
  out & _pointer;
}

void Simple::deserialize(tsl::IArchive& in) {
  in & _pointer;
}

bool Simple::operator==(const Simple &other) const {
  return (((_pointer && other._pointer)||(!_pointer && !other._pointer)) && (*_pointer==*other._pointer));
}

}

using namespace std;
using namespace example_5;

int main(int argc,const char * argv[]) {
  try {
    // the program is aware of the classes it will use so let's register them
    rtx::ClassInfo::registry().add<ManuallyRegistered>();
    // let's allocate the serializable instance
    unique_ptr<ManuallyRegistered> p(new ManuallyRegistered("shared object #1"));
    const Simple o1(p.get()), o2(p.get());
    // create a stream where serialize the objects
    std::stringstream stream;
    {
      tsl::OJsonArchive archive(stream);
      archive & o1 & o2;
    }
    // dump of the serialization result
    cout << stream.str() << endl;
    // create 2 new instances and let's initialize them deserializing the stream
    Simple r1,r2;
    {
      stream.unsetf(ios::skipws);
      tsl::IJsonArchive archive(stream);
      archive & r1 & r2;
    }
    // check the post-condition
    assert((o1==r1) && (o2==r2));
    cout << "the restored object match the originals ones!" << endl;
  } catch (const runtime_error & error) {
    cerr << "Exception: " << error.what() << endl;
  }
}

Method 1 - Proxy

With the Proxy template the main function of the example above will be modified as in the following:

int main(int argc,const char * argv[]) {
  try {
    // the program is aware of the classes it will use so let's register them
    rtx::ClassInfo::Proxy<ManuallyRegistered> proxy;
    // let's allocate the serializable instance
    unique_ptr<ManuallyRegistered> p(new ManuallyRegistered("shared object #1"));
    const Simple o1(p.get()), o2(p.get());
    // create a stream where serialize the objects
    std::stringstream stream;
    {
      tsl::OJsonArchive archive(stream);
      archive & o1 & o2;
    }
    // dump of the serialization result
    cout << stream.str() << endl;
    // create 2 new instances and let's initialize them deserializing the stream
    Simple r1,r2;
    {
      stream.unsetf(ios::skipws);
      tsl::IJsonArchive archive(stream);
      archive & r1 & r2;
    }
    // check the post-condition
    assert((o1==r1) && (o2==r2));
    cout << "the restored object match the originals ones!" << endl;
  } catch (const runtime_error & error) {
    cerr << "Exception: " << error.what() << endl;
  }
}

Method 2 - static Proxy

 Below the modified code in order to use a static proxy instance so that the class registration will persist during all the program lifetime. 

// a static proxy instance will ensure proper registration during the whole program lifetime.
rtx::ClassInfo::Proxy<ManuallyRegistered> proxy;

int main(int argc,const char * argv[]) {
  try {
    // let's allocate the serializable instance
    unique_ptr<ManuallyRegistered> p(new ManuallyRegistered("shared object #1"));
    const Simple o1(p.get()), o2(p.get());
    // create a stream where serialize the objects
    std::stringstream stream;
    {
      tsl::OJsonArchive archive(stream);
      archive & o1 & o2;
    }
    // dump of the serialization result
    cout << stream.str() << endl;
    // create 2 new instances and let's initialize them deserializing the stream
    Simple r1,r2;
    {
      stream.unsetf(ios::skipws);
      tsl::IJsonArchive archive(stream);
      archive & r1 & r2;
    }
    // check the post-condition
    assert((o1==r1) && (o2==r2));
    cout << "the restored object match the originals ones!" << endl;
  } catch (const runtime_error & error) {
    cerr << "Exception: " << error.what() << endl;
  }
}
Joomla templates by a4joomla