Sending and returning custom classes

We have seen previously that all STL containers can be serialized and deserialized by Thallium. Thallium can also serialize and deserialize user-defined classes, with a little help. This help comes in the form of a serialize function template put inside the class.

3D point example

Here is an example with a point class representing a 3d point.

class point {

    private:

        double x;
        double y;
        double z;

    public:

        point(double a=0.0, double b=0.0, double c=0.0)
        : x(a), y(b), z(c) {}

        template<typename A>
        void serialize(A& ar) {
             ar & x;
             ar & y;
             ar & z;
        }
};

You will also need the class to be default-constructible.

The template parameter (A) and the function’s parameter (ar) represent an archive, from which one can serialize/deserialize data. Note that you don’t need to provide two separate functions: Thallium is smart enough to use the same serialize function for both reading and writing, depending on the context.

The serialize function calls the operator & of the archive to either write or read the class’ data members. Provided that those data members are basic types, user-defined types with a serialize method, or STL containers of a serializable type, Thallium will know how to serialize the class.

Important

Thallium needs classes used as RPC argument/response to be default-constructible (i.e. provide a constructor that takes no argument).

Asymmetric serialization

In some cases it may not be possible for the serialize function to present a symmetric behavior for reading and for writing. For instance this may happen if the deserialization function needs to allocate a pointer. In these cases, one can, instead of a serialize method, provide a save method and a load method. save will be used for serialization, load will be used for deserialization.

Reading/writing raw data

It may also be convenient to read or write raw data from/to the archive. For this, note that archive objects used for serialization provide a write method:

template<typename T> write(T* const t, std::size_t count=1)

and archive objects used for deserialization provide a read method:

template<typename T> read(T* t, std::size_t count=1)

The write method should be used only in a save template method, while the read method should be used only in a load template method.

Non-member serialization functions

In some contexts it may not be possible to add member functions to a class. In those cases, you can add serialize or load/save functions outside of the class, as follows:

template<typename A>
void serialize(A& ar, MyType& x) {
  ...
}

or

template<typename A>
void save(A& ar, const MyType& x) {
  ...
}

template<typename A>
void load(A& ar, MyType& x) {
  ...
}

Type checking

Contrary to Margo, where types (structures) and their serialization function will generally be defined in a header file shared by the client and the server, Thallium’s template based serialization leaves the user open to the risk of a type mismatch between the client and the server. During development, we recommand to build your code with -DTHALLIUM_DEBUG_RPC_TYPES, or if you use cmake, to link your binaries/libraries against the thallium_check_types target using target_link_libraries (mylib PRIVATE thallium_check_types). This flag will make thallium add the name of the type it is serializing to the serialization buffer. This adds an overhead but allows the receiving end to check that the name of the types match, and to print an error if they don’t.