Get private data members for non intrusive boost serialization C++
Asked Answered
D

3

8

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 ?

Diphyodont answered 2/6, 2015 at 11:32 Comment(0)
I
14
  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

Imhoff answered 2/6, 2015 at 11:57 Comment(4)
Added a "best-of-both-worlds" approach that doesn't break encapsulation: Live On Coliru.Imhoff
wow. this is a really nice answer offerring different solutions with their pros and cons. Excactly what I was hopping for ;). Thank you ! Too bad I cannot vote twice... I will give 1/ and 3/ a try !Diphyodont
As always, a complete great answer. Also, while not breaking encapsulation, and likely a complete violation of trust, this approach demonstrates serializing private member variables. As best as I can tell, it is spec compliant. I have sadly had to use this when dealing with third-party libraries. ):Petrography
Thank you Tanner for your solution I will take a look !Diphyodont
O
1

Just for additional info: In order to get the first solution from sehe working:

You need a forward decleration of the friends method like this:

// Boost
#include <boost/serialization/access.hpp>

class ClassB;

namespace boost{
namespace serialization {
    template <typename Ar> void serialize(Ar&,ClassB&,const unsigned);
}
}

class ClassB: public ClassA{

private:
    template <typename Ar> friend void boost::serialization::serialize(Ar&,ClassA&,const unsigned);
public:
    ClassA();
    virtual ~ClassA();
};

Took me a while to get it working.

Cheers

Ovation answered 14/12, 2016 at 17:40 Comment(1)
Actually, to get that sample working, you just click on the link that says Live On Coliru and see you don't need the forward declarationImhoff
S
0

Supplementary Information to sehe's first solution:

The solution requires two-phase lookup and or argument dependent lookup. Unfortunately, MSVC does not yet support this to its full extent.

Compiling this in VS Community 2019 16.1.6 with boost 1.70 results in an obscure error:

Error   C2063    'boost::serialization::serialize': not a function

Even though conformance mode is enabled through the /permissive- flag and the latest language standard /std::c++latest is selected, as described in this MSVC Blog Post.

Adding the typename qualifier to the friend declaration solves the problem:

template <typename Ar, typename U> friend void boost::serialization::serialize(typename Ar&, A<U>&, const unsigned);

Even more interestingly frustratingly:

if class A is not a templated class, then it doesn't work either way, same error as above... Example code: http://coliru.stacked-crooked.com/a/ecfbb39d5975d753

Swearingen answered 10/7, 2019 at 16:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.