Search code examples
c++serializationboostdeserializationstdarray

Boost - deserialize an array only when sizes match


I'd like deserialization to fail when size of the array that I'm trying to deserialize to does not match the originally serialized array's size.

So far it only fails when arr1_size > arr2_size and I want it to be arr1_size != arr2_size:

#include <iostream>
#include <sstream>
#include <array>

#include <boost/serialization/serialization.hpp>
#include <boost/serialization/array.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>

int main()
{
    const size_t arr1_size = 4, arr2_size = 3;
    std::stringstream ss;

    // save
    std::array<int, arr1_size> arr1;
    boost::archive::text_oarchive oar(ss, boost::archive::no_header);
    oar & arr1;

    // load
    std::array<int, arr2_size> arr2;
    boost::archive::text_iarchive iar(ss, boost::archive::no_header);
    iar & arr2; // throw on size inequality, please
}

Live on Coliru


I thought about:

  • serializing through std::vectors and handling this myself, but that might lead to performance loss

  • checking arr2 afterwards (if it doesn't throw on arr1_size > arr2_size) for trailing default-constructed class type elements or otherwise special values) to handle arr1_size < arr2_size

Is there anything simpler, preferably provided by boost that I've missed?


Solution

  • Here is the Boost code you want to bypass, specifically this test is insufficient:

        if(static_cast<std::size_t>(count) > current_count)
            boost::serialization::throw_exception(
                archive::archive_exception(
                    boost::archive::archive_exception::array_size_too_short
                )
            );
    

    One workaround is to substitute your own serialization for std::array. This is easiest if you can avoid including the header boost/serialization/array.hpp for any translation unit that you serialize std::array. It is still possible if you need that header file (e.g. to serialize ordinary arrays) - the trick to avoid matching the Boost templated function:

    template <class Archive, class T, std::size_t N>
    void serialize(Archive& ar, std::array<T,N>& a, const unsigned int /* version */)
    ...
    

    One way to do this is to explicitly specify your element type:

    typedef int MyArrayElementType;
    
    namespace std {
       template<class Archive, size_t N>
       void serialize(Archive& ar, std::array<MyArrayElementType, N>& a, const unsigned int version)
       ...
    

    Here's an adaptation of your MCVE:

    #include <iostream>
    #include <sstream>
    #include <array>
    
    #include <boost/serialization/serialization.hpp>
    #include <boost/serialization/split_free.hpp>
    #include <boost/serialization/array.hpp>
    #include <boost/archive/text_oarchive.hpp>
    #include <boost/archive/text_iarchive.hpp>
    
    // Supply your element type here.
    typedef int MyArrayElementType;
    
    namespace std {
       template<class Archive, size_t N>
       void serialize(Archive& ar, std::array<MyArrayElementType, N>& a, const unsigned int version) {
          boost::serialization::split_free(ar, a, version);
       }
    
       template<class Archive, size_t N>
       void save(Archive& ar, const std::array<MyArrayElementType, N>& a, const unsigned int version) {
          // Adapted code from oserializer.hpp save_array_type::invoke().
          boost::serialization::collection_size_type count(N);
          ar << BOOST_SERIALIZATION_NVP(count);
          ar << boost::serialization::make_array(static_cast<MyArrayElementType const*>(&a[0]), count);
       }
    
       template<class Archive, size_t N>
       void load(Archive& ar, std::array<MyArrayElementType, N>& a, const unsigned int version) {
          // Adapted code from iserializer.hpp load_array_type::invoke().
          boost::serialization::collection_size_type count;
          ar >> BOOST_SERIALIZATION_NVP(count);
          if(static_cast<std::size_t>(count) != N)
             boost::serialization::throw_exception(
                std::runtime_error("std::array size mismatch")
                );
          ar >> boost::serialization::make_array(static_cast<MyArrayElementType*>(&a[0]), count);
       }
    }
    
    int main()
    {
        const size_t arr1_size = 3, arr2_size = 4;
        std::stringstream ss;
    
        // save
        std::array<int, arr1_size> arr1;
        boost::archive::text_oarchive oar(ss, boost::archive::no_header);
        oar & arr1;
    
        // load
        std::array<int, arr2_size> arr2;
        boost::archive::text_iarchive iar(ss, boost::archive::no_header);
        iar & arr2; // throw on size inequality, please
    }
    

    Live on CoLiRu

    This uses the same array serialization machinery that the built-in serialization does, so it should have exactly the same performance. If you are able to remove boost/serialization/array.hpp you could change MyArrayElementType to a template argument instead.