1

I'm trying to build a class that will act as a base class for any type I want to serialize in a private project I'm doing.

I'm trying to make the class work with at least boost serialization archives and QDataStream by providing functionality for '<<' and '>>'. Any other stream that will work with the class is just a bonus.

Important: I'll probably only use QDataStream. I'm building this class more as a puzzle/opportunity to learn (which seems to work) so while I would appreciate even workarounds that completely deviate from this form, I would very much like it if things could work the way I wanted them to (as close as possible given the restrictions of the language, of course), gaining some knowledge on the way.

The class as I thought it would be:

#ifndef SERIALIZABLE_H
#define SERIALIZABLE_H

#include <QObject>
#include <QDataStream>

#include <boost/serialization/access.hpp>
#include <boost/serialization/version.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/split_member.hpp>

// #include boost stl containers handlers...

class Serializable : public QObject
{
    Q_OBJECT

    template <typename Archive>
    virtual void Serializable_save( Archive &out, const quint32 p_version = 0 ) = 0;
    template <typename Archive>
    virtual void Serializable_load( Archive &in, const quint32 p_version = 0 ) = 0;

    quint32 m_ID;
    quint16 m_version;

  public:
    explicit Serializable( QObject *parent = 0 ) : QObject( parent ) {}

    BOOST_SERIALIZATION_SPLIT_MEMBER()

    template <typename Archive>
    void save( Archive &out, const quint32 p_version = 0 )
    {
        out << m_ID << m_version;
        Serializable_save( out, p_version );
    }

    template <typename Archive>
    void load( Archive &in, const quint32 p_version = 0 )
    {
        in >> m_ID >> m_version;
        Serializable_load( in, p_version );
    }

    quint32 ID() const;
    void setID( const quint32 &ID );

    quint16 version() const;
    void setVersion( const quint16 &version );
};

template <typename Archive>
Archive &operator << ( Archive &out, const Serializable &module )
{
    module.save( out );
    return out;
}

template <typename Archive>
Archive &operator >> ( Archive &in, Serializable &module )
{
    module.load( in );
    return in;
}

#endif // SERIALIZABLE_H

I immediately discovered that virtual templates are not allowed and met the new term (for me) "type erasure".

I tried to employ type erasure after reading this article: On the Tension Between Object-Oriented and Generic Programming in C++ and What Type Erasure Can Do About It (up to and including "Beyond boost::any")...

Unsuccessfully.

A few notes:

Serializable_save & Serializable_load are part a naming convention that goes down the inheritance and allows for multilevel NVI. (Multilevel NVI is just a name I gave for the concept of finalizing the virtual function inherited from the base class and providing a new virtual function for inheritors. Allowing for a set of actions to always take place all the way down the inheritance chain) That means that an inheriting class that is not an end class will look like this:

#ifndef DATAMODULE_I_H
#define DATAMODULE_I_H

#include <StorageGateway/serializable.h>

class DataModule_I : public Serializable
{
    template <typename Archive>
    virtual void DataModule_I_save( Archive &out ) = 0;
    template <typename Archive>
    virtual void DataModule_I_load( Archive &in ) = 0;

    template <typename Archive>    
    virtual void Serializable_save( Archive &out ) final
    {
        // Some preconditions.
        DataModule_I_save( out );
        // Some postconditions.
    }

    template <typename Archive>
    virtual void Serializable_load( Archive &in ) final
    {
        // Some preconditions.
        DataModule_I_load( in );
        // Some postconditions.
    }

  public:
    explicit DataModule_I( const quint32 ID, QObject *parent = 0 );
};

#endif // DATAMODULE_I_H

My next attempt was to bury the 'Archive' templates inside a StreamWrapper class (similar to type erasure but not quite it) in order to eliminate the immediate need for a template and pass the compiler's no-go concerning virtual templates' infinity problem.

Of course, that did not work. As I ended up needing to specify the template types which is exactly the opposite of what I was trying to achieve.

I'm using C++14 if it matters (between 11 and 14, I mean).

I still assume Type Erasure is the answer. I just don't understand how to use it.

So,

  • Is it possible to achieve the behavior I want? If yes:
  • How can I achieve a class that will allow this "Multilevel NVI" behavior when virtual templates are illegal?

EDIT: I think this might be a solution but I can`t properly test it yet.

user2962533
  • 397
  • 1
  • 5
  • 18
  • You may want to look at boost polymorphic archives – n. m. could be an AI Dec 15 '15 at 15:33
  • @n.m. Thanks for the pointer :) – user2962533 Dec 15 '15 at 15:36
  • Also explore how boost serialization handles derived classes serialized through base class pointers (with regular non-polymorphic archives). – n. m. could be an AI Dec 15 '15 at 15:44
  • @n.m. Are you hinting at a mistake I made? I know boost serialization uses "base_object" but I thought it'd be better to it this way. Although I'm really not sure - and trying to figure out - if my method won`t break the duplicates check... – user2962533 Dec 15 '15 at 15:58
  • Not to mention that the flow direction is crucial for ensuring common parts are serialized (from base classes) - which I believe can't be done in reverse unless I specify them manually in every single class... – user2962533 Dec 15 '15 at 16:00
  • Do you have an example of boost serialization not working because of an inherent design problem in boost? – n. m. could be an AI Dec 15 '15 at 16:02
  • @n.m. I'm afraid I don't understand your question. – user2962533 Dec 15 '15 at 16:03
  • @n.m. What I *can* say is that boost requires you to put a "base_object" piece in every serialization function down the inheritance. Unless absolutely necessary - I want it gone. Also, see my comment about the direction of the serialization flow. – user2962533 Dec 15 '15 at 16:06
  • You have departed from the way boost serialization works (it does not use virtual "serialize" functions, while you do). Do you have a reason for this departure? Is there a case you tried to implement "by the book" using boost::serialization? – n. m. could be an AI Dec 15 '15 at 16:08
  • What do you mean by "base object piece"? – n. m. could be an AI Dec 15 '15 at 16:09
  • @n.m. First, allow me to remind (hopefully) you that this entire thing is a self-made puzzle more than anything else and likely has no actual use. If this seems like a waste of time for you, I will understand. Now, as for your question: The reason for using NVI for serialization is to try and generalize as much of the process as possible (no need for "base_object" every time and allow for infinite inheritance through mid-classes until the end classes). – user2962533 Dec 15 '15 at 16:11
  • @n.m. I mean this: http://www.boost.org/doc/libs/1_55_0/libs/serialization/doc/tutorial.html @ derived classes – user2962533 Dec 15 '15 at 16:15
  • If you have class A that inherits from B that inherits from C, you have to call A serialization, B serialization and C serialization *somewhere*. Boost wants A serialization to call B serialization which calls C serialization. If you don't want this, how do you plan to handle it? – n. m. could be an AI Dec 15 '15 at 16:16
  • By using a chain of NVI. C has the public interface "save" which saves variables common to all classes inheriting from it and a virtual function for its inheritors to override and put their own variables for serialization in. Class B then overrides this virtual function, serializing its own variables and calling its own declared virtual function that it set for its inheritors to override and so on. – user2962533 Dec 15 '15 at 16:19
  • If you mean that this boost::serialization::base_object syntax is inconvenient and you would prefer calling the base serialization function directly, you should understand that there is a reason behind this inconvenient syntax. If you try to devise a better system without understanding this reason in full, you are bound to trip over it again. – n. m. could be an AI Dec 15 '15 at 16:22
  • @n.m. I got hopeful you are going to explain it for a second... Do you know any place I can read about how boost's duplicate detection works so that I will know if my method screws it up or not? My other option is to ask it here on SO. – user2962533 Dec 15 '15 at 16:24
  • I don't know of any design document about boost::serialization, but you can always peek in the source. It is not that complicated. It writes all encountered base/derived pointer pairs in a table. If you build your hierarchy with CRTP you probably can call `boost::serialization::void_cast_register` in the "NVI" wrapper instead of calling `base_object`, but I'm not too sure about it. Anyway, why `archive & base_object(*this)` is so much less desirable than `mybase::serialize(archive)`? – n. m. could be an AI Dec 15 '15 at 16:53
  • Also if you want to only serialize polymorphic classes, you may be able to get away with not calling base_object and instead registering the `dynamic_cast(this)` as the "base" of this (again, not so sure). – n. m. could be an AI Dec 15 '15 at 17:13
  • @n.m. "Anyway, why archive & base_object(*this) is so much less desirable than mybase::serialize(archive)?" - It's not. I wanted to get rid of the necessity altogether. If that proves to be impossible I'll just have to go the way boost designed it to be. I'm not sure about casting everything to void*. Although this class is supposed to act as a base class for any **class** I make so that it would be serializable (Serializable, got it?). – user2962533 Dec 15 '15 at 17:45
  • You need to serialize the base class somewhere, somehow. I don't understand how you plan to get rid of the necessity. "Serializable" is not an inheritable property, it cannot be made into a base class, sadly. – n. m. could be an AI Dec 15 '15 at 19:26
  • @n.m. Of course I give every class its own definition for serialization. What I'm getting rid of is any repetitiveness that I can from the process. Among such repetitions is calling to base_object and any other thing that I can stuff higher up the hierarchy only once - in the base class instead of having to redeclare every single time in every class. Sure, I made myself declare virtual functions but the thing is, my method is much less error-prone; You can't "forget" to implement a function you have to. Unlike base_object which can just slip your mind. – user2962533 Dec 15 '15 at 19:42
  • Also, to correct myself a little: The purpose of this class is to allow any class inheriting from it to provide only one piece of serialization logic that will apply to any stream using bitwise operators. ....While supporting inheritance of base classes serialization logic. – user2962533 Dec 15 '15 at 19:45
  • OK you write class A that inherits Serializable. You implement the virtual function because you have to. Then you write class B that inherits A. You don't have to implement anything for B, and when you do remember to implement serialization for it, you are free to implement it incorrectly (without calling A::serialize). What have you achieved exactly? – n. m. could be an AI Dec 15 '15 at 20:07
  • @n.m. I **do** have to implement a function for B. See the example I gave in the question. I agree with you that the benefits are minimal at best (Did I already mention that this is a puzzle?) but they're still there. I'm referring to the NVI chain - which I need to declare for every class time and time again. !!!How can I implement B's serialization incorrectly (without calling A's)? My serialization direction is up->down. Serializable->A->B. This is true for boost as well but only if you remember to put base_object at the beginning. – user2962533 Dec 15 '15 at 20:18
  • MM... Anyway, even if my method turns out to be completely useless, the class still does some good by funneling all streams to the same serialization logic. – user2962533 Dec 15 '15 at 20:30
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/98020/discussion-between-user2962533-and-n-m). – user2962533 Dec 15 '15 at 20:35

0 Answers0