11.3. Serializing user defined types - non-intrusive split

The general technique is to define two functions named "save" and "load" in the ccs::serialization namespace, and they must take a "serializer object" that can be of any type, and a reference to the class that is to be serialized. For saving, the "save" function must pass to the "serializer object" the members of the class instance that must be stored. For loading, temporary objects are created, one for each member that is to be loaded, those objects are loaded, and then a new instance of the class is instantiated and then copied to the object reference that was passed to "load".

For example, a banking program manages instances of this bank_account class:

class bank_account {
public:
  bank_account(int id, float balance) : id_(id), balance_(balance) {
    setup_interest_rate();
  }

  int get_id(void) const { return id_; }
  float get_balance(void) const { return balance_; }

private:
  int id_;        // this is a PUBLIC member
  float balance_; // this is a PUBLIC member

  // this depends on the balance, so it does not need to be serialized
  float interest_rate_;
};

The following code saves instances of the bank_account class. Notice the use of operator<<.

namespace ccs {
  namespace serialization {
    template <typename S>  void  save(S& s, const bank_account& ba) {
      s << ba.get_id() << ba.get_balance();
    }
  }
}

The following code loads instances of the bank_account class. Notice the use of operator>>.

namespace ccs {
  namespace serialization {
    template <typename S>  void  load(S& s, bank_account& ba) {
      // load data into temporary variables...
      int id;
      float balance;
      s >> id >> balance;

      // ...then construct a new instance of bank_account, and in
      // doing so, the interest rate will be correctly set up. At
      // the same time, copy the new instance to the object that
      // is supposed to receive the loaded data:
      ba = bank_account(id, balance);
    }
  }
}

Notice that because the function is outside the class, the members must be readable so that saving can work. Thus the "save" function uses the accessor member functions to obtain the values to store.

The constructor of the bank account class must be default-constructible, otherwise it will not be possible to load a vector, say, of bank_account instances. Thus:

class bank_account {
public:
  bank_account(int id = 0, float balance = 0) :
    id_(id), balance_(balance) {
    setup_interest_rate();
  }

  ...
};