c++boostboost-serialization

How to know if shared_ptr was already serialized to boost archive


In my program I have c++ class objects that keep SmartPointers members (SmartPointer is my own custom class derived from boost::shared_ptr). By design, some of my class objects must keep SmartPtr that are unique i.e no shared ownership is allowed.

I want to implement check module for debug reasons that will test whether given c++ class object (the c++ object can contain nested c++ class object members on its own) keeps smart pointers with shared ownership. If yes, I want to throw exception. I was thinking to reuse the serialization lib for this purpose since I already have serialize functions for all my classes. All I have to do is add checking code in my SmartPointer::serialize function to test whether the pointer was already saved in the archive. So anyone is aware of such function in boost serialization that will tell me whether pointer object was already serialized? I think that the boost serialization library must have mechanism to check this, since in the archive output shared_ptrs with shared ownerships are written just once.


Solution

  • The code smell is using a shared_ptr where unique ownership must be guaranteed. What are you going to do when you find a violation? Assert? std::terminate?

    You could make a serialization wrapper that adds the check on de-serialization.

    You will depend on library implementation details, because - apparently - while loading there will be a temporary copy of the shared_ptr:

    Live On Coliru

    #undef NDEBUG
    #include <boost/archive/text_oarchive.hpp>
    #include <boost/archive/text_iarchive.hpp>
    #include <boost/serialization/shared_ptr.hpp>
    #include <boost/serialization/wrapper.hpp>
    #include <iostream>
    
    template <typename SP> struct Unique {
        SP& _ref;
        Unique(SP& r):_ref(r){}
    
        template <typename Ar>
        void serialize(Ar& ar, unsigned) {
            if constexpr (typename Ar::is_saving{}) {
                if (_ref && not _ref.unique())
                    throw std::logic_error("Shared ownership not allowed");
            }
            ar & _ref;
            if constexpr (typename Ar::is_loading{}) {
                if (_ref && _ref.use_count() != 2)
                    throw std::logic_error("Shared ownership not allowed");
            }
        }
    };
    
    template <typename SP>
    struct boost::serialization::is_wrapper<Unique<SP>> : boost::mpl::true_ {};
    
    struct Data {
        int a,b,c;
        void serialize(auto& ar, unsigned) { ar & a & b & c; }
    };
    
    static std::string do_write(auto const&... data) {
        std::ostringstream            oss;
        boost::archive::text_oarchive oa(oss);
        (oa << ... << data);
        return oss.str();
    }
    
    static void do_read(std::string const& str, auto&&... data)
    {
        std::istringstream            iss(str);
        boost::archive::text_iarchive ia(iss);
        (ia >> ... >> data);
    }
    
    int main()
    {
        auto data = std::make_shared<Data>(1, 2, 3);
        assert(data.unique());
    
        // okay:
        {
            auto txt = do_write(data, data, data);
            do_read(txt, data);
            std::cout << "L:" << __LINE__ << " Success " << std::endl;
        }
    
        // still okay:
        {
            Unique ud{data};
            auto txt = do_write(ud);
            do_read(txt, ud);
            std::cout << "L:" << __LINE__ << " Success " << std::endl;
            assert(data.unique());
        }
    
        // not okay because not unique:
        try
        {
            auto data_shared(data);
            Unique ud{data}, ud_shared{data_shared};
            assert(!data.unique());
            assert(!data_shared.unique());
    
            auto txt = do_write(ud, ud_shared);
            do_read(txt, ud, ud_shared);
            std::cout << "L:" << __LINE__ << " Success " << std::endl;
        }catch(std::exception const& e) {
            std::cout << "L:" << __LINE__ << " Failure " << e.what() << std::endl;
        }
    }
    

    Prints

    L:57 Success 
    L:65 Success 
    L:81 Failure Shared ownership not allowed
    

    Summary

    Don't do this, because you can't cleanly do it. The alternative is far worse (delving into the object tracking implementation details as some commenters suggested).

    Simply