Search code examples
c++serializationboostboost-serialization

Get private data members for non intrusive boost serialization C++


I have tried providing getters of class A for my non-member serialize() function` since accessing from members is private.

template<typename T>
class A
{
public:
  A(const T& id) : m_id(id) {}
  T& getRef() { return m_id; } // not giving good results
  T  getId()  { return m_id; } // not giving good results
  const T& getRef() const { return m_id; } // not giving good results
private: // I would like to keep it private
  T m_id;
}

namespace boost { namespace serialization {

template<class Archive,typename T>
void serialize(Archive &ar, A &a, const unsigned int version)
{
    // ar &BOOST_SERIALIZATION_NVP(a.m_id); // I would like to avoid that it works if m_id is public
    ar &BOOST_SERIALIZATION_NVP(a.GetRef()); // I want this !
}

}}

// and later I use
std::ofstream ofs("test.xml");
boost::archive::xml_oarchive oa(ofs);
A<int> a(42);
oa << BOOST_SERIALIZATION_NVP(a);

Unfortunately the execution keeps telling me uncaught exception of type boost::archive::xml_archive_exception - Invalid XML tag name when I try to use getters either GetRef()or GetId().
It works well if I access directly to m_id when it is public.

Are there any nice ways of doing so ?


Solution

    1. You can use good old-fashioned friends:

      Live On Coliru

      template <typename T>
      class A {
        public:
          A(const T &id) : m_id(id) {}
        private:
          template <typename Ar, typename U> friend void boost::serialization::serialize(Ar&,A<U>&,const unsigned);
          T m_id;
      };
      
      namespace boost {
      namespace serialization {
          template <class Archive, typename T>
          void serialize(Archive &ar, A<T> &a, const unsigned int)
          {
              ar & BOOST_SERIALIZATION_NVP(a.m_id);
          }
      }
      }
      

    2. You can use the getRef() approach. This

      • requires no friends (less intrusive)
      • requires make_nvp (because you can't use a.getRef() as an XML element name

      Sadly, having the reference getter break encapsulation in a horrific way. I'd personally prefer to have m_id public in the first place, instead.

      Live On Coliru

      template <typename T>
      class A {
      public:
          A(const T &id) : m_id(id) {}
      
          T& getRef()             { return m_id; } 
          T const& getRef() const { return m_id; } 
      private:
          T m_id;
      };
      
      namespace boost {
      namespace serialization {
          template <class Archive, typename T>
          void serialize(Archive &ar, A<T> &a, const unsigned int)
          {
              ar & boost::serialization::make_nvp("m_id", a.getRef());
          }
      }
      }
      

      Bonus points:

    3. You can use a 'pimpl' style struct. You can forward declare a struct inside A<>:

      template <typename T>
      class A {
      public:
          struct access;
      
          A(const T &id) : m_id(id) {}
      private:
          T m_id;
      };
      

      That's less intrusive than the getRef() approach which simply breaks encapsulation all the way. Now, you can hide the private access inside this class:

      namespace boost {
      namespace serialization {
          template <class Archive, typename T>
          void serialize(Archive &ar, A<T> &a, const unsigned int version)
          {
              A<T>::access::serialize(ar, a, version);
          }
      }
      }
      

      Of course you still need to implement it, but this can be done in a separate header and doesn't influence class A<> (or any of its specializations) at all:

      template <typename T>
      struct A<T>::access {
          template <class Archive>
          static void serialize(Archive &ar, A<T> &a, const unsigned int) {
              ar & BOOST_SERIALIZATION_NVP(a.m_id);
          }
      };
      

      See it Live On Coliru as well