Search code examples
c++serializationboost

Custom boost serialization input archive fails only with vectors (boost::archive::archive_exception)


I'm working with boost serialization and I have written a custom input archive that derives from boost's binary input archive. Event though this class is for now doing exactly the same as boost's binary_iarchive, I get a boost::archive::archive_exception when deserializing std::vectors of primitive types. I don't get any issues serializing, e.g. an std::string, or an std::vector<std::string>.

Here is the full code:

The InputArchive implementation:

#ifndef __INPUT_ARCHIVE_H
#define __INPUT_ARCHIVE_H

#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/impl/basic_binary_iprimitive.ipp>
#include <boost/archive/impl/basic_binary_iarchive.ipp>

class InputArchive;

using iarchive = boost::archive::binary_iarchive_impl<
    InputArchive,
    std::istream::char_type,
    std::istream::traits_type>;

class InputArchive : public iarchive {

    friend class boost::archive::detail::interface_iarchive<InputArchive>;
    friend class boost::archive::basic_binary_iarchive<InputArchive>;
    friend class boost::archive::load_access;

    public:

    template<typename ... Args>
    InputArchive(Args&& ... args)
    : iarchive(std::forward<Args>(args)..., boost::archive::archive_flags::no_header)
    {}

};

#endif

Dummy class, for testing purpose:

#ifndef __DUMMY_PRODUCT_H
#define __DUMMY_PRODUCT_H

#include <vector>
#include <boost/serialization/vector.hpp>
#include <string>
#include <boost/serialization/string.hpp>

struct dummy_product {
    std::vector<char> data;

    template<typename A>
    void serialize(A& ar, const unsigned int version) {
        ar & data;
    }

};

#endif

And example code using the archive:

#include <boost/archive/binary_oarchive.hpp>
#include "InputArchive.hpp"
#include "DummyProduct.hpp"
#include <string>
#include <sstream>
#include <iostream>

int main() {

    dummy_product p;
    p.data.resize(16);

    std::string buffer;
    {
        std::stringstream ss_value;
        boost::archive::binary_oarchive oa(ss_value, boost::archive::archive_flags::no_header);
        oa << p;
        buffer = ss_value.str();
    }
    for(auto i : buffer) {
        std::cout << (int)i << " ";
    }
    std::cout << std::endl;

    {
        std::stringstream ss_value(buffer);
        //boost::archive::binary_iarchive ia(ss_value, boost::archive::archive_flags::no_header);
        InputArchive ia(ss_value);
        ia >> p;
    }

}

This code throws the following exception:

terminate called after throwing an instance of 'boost::archive::archive_exception'
  what():  input stream error

As you can see, the InputArchive simply inherits from the appropriate boost::archive::binary_iarchive_impl and does nothing more. If I replace the InputArchive with a boost::archive::binary_iarchive there is no issue. If I use std::string instead of std::vector<char> in dummy_product there is no issue either. The exception seems to be happening only for std::vector of primitive types.

Any idea where this problem comes from? I'm using boost 1.75.0 and gcc 8.3.0.


Solution

  • There are two major things missing:

    1. Q. As you can see, the InputArchive simply inherits from the appropriate boost::archive::binary_iarchive_impl and does nothing more.

      A. Yeah, but not in the appropriate way. Your constructor forgets to initialize the archive. Just forwarding to the base class constructor is half the work:

      enum {
          forced_flags = boost::archive::archive_flags::no_header
      };
      
      InputArchive(std::istream & is, unsigned int flags = 0) :
          iarchive(is, flags | forced_flags)
      {
          init(flags | forced_flags);
      }
      InputArchive(std::streambuf & bsb, unsigned int flags = 0) :
          iarchive(bsb, flags | forced_flags)
      {
          init(flags | forced_flags);
      }
      
    2. Next up, there is an optimization for primitive types for archives that allow bitwise binary output. Yours is one of them, so you need tell Boost about it:

      BOOST_SERIALIZATION_USE_ARRAY_OPTIMIZATION(InputArchive)
      
    3. To be really cool, you probably also want to correctly have type inforation for polymorphic types registered with your archive type. If you do, you want to tell Boost about it as well:

      BOOST_SERIALIZATION_REGISTER_ARCHIVE(InputArchive)
      

    Loose notes:

    • I'm not sure what the friends are for. This could be my character flaw
    • Consider moving things into namespaces so e.g. ::iarchive isn't polluting the global namespace for no reason. [Also, consider just going with CRTP and spelling the name out inside the class definition.]

    Now It Works

    Live On Wandbox

    #ifndef __INPUT_ARCHIVE_H
    #define __INPUT_ARCHIVE_H
    
    #include <boost/archive/binary_iarchive.hpp>
    #include <boost/archive/impl/basic_binary_iprimitive.ipp>
    #include <boost/archive/impl/basic_binary_iarchive.ipp>
    
    class InputArchive;
    
    using iarchive = boost::archive::binary_iarchive_impl<
        InputArchive,
        std::istream::char_type,
        std::istream::traits_type>;
    
    class InputArchive : public iarchive {
        enum {
            forced_flags = boost::archive::archive_flags::no_header
        };
    
        //friend class boost::archive::detail::interface_iarchive<InputArchive>;
        //friend class boost::archive::basic_binary_iarchive<InputArchive>;
        //friend class boost::archive::load_access;
      public:
        InputArchive(std::istream & is, unsigned int flags = 0) :
            iarchive(is, flags | forced_flags)
        {
            init(flags | forced_flags);
        }
        InputArchive(std::streambuf & bsb, unsigned int flags = 0) :
            iarchive(bsb, flags | forced_flags)
        {
            init(flags | forced_flags);
        }
    };
    
    // required by export
    BOOST_SERIALIZATION_REGISTER_ARCHIVE(InputArchive)
    BOOST_SERIALIZATION_USE_ARRAY_OPTIMIZATION(InputArchive)
    
    #endif
    
    #ifndef __DUMMY_PRODUCT_H
    #define __DUMMY_PRODUCT_H
    
    #include <vector>
    #include <boost/serialization/vector.hpp>
    #include <string>
    #include <boost/serialization/string.hpp>
    
    struct dummy_product {
        std::vector<char> data;
    
        template<typename A>
        void serialize(A& ar, unsigned) {
            ar & data;
        }
    
    };
    
    #endif
    
    #include <boost/archive/binary_oarchive.hpp>
    //#include "InputArchive.hpp"
    //#include "DummyProduct.hpp"
    #include <string>
    #include <sstream>
    #include <iostream>
    #include <iomanip>
    
    int main() {
    
        dummy_product p;
        p.data.resize(16);
    
        std::string buffer;
        {
            std::stringstream ss_value;
            boost::archive::binary_oarchive oa(ss_value, boost::archive::archive_flags::no_header);
            oa << p;
            buffer = ss_value.str();
        }
        for(int i : buffer) {
            std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)i << " ";
        }
        std::cout << std::endl;
    
        {
            std::stringstream ss_value(buffer);
            // boost::archive::binary_iarchive ia(ss_value, boost::archive::archive_flags::no_header);
            InputArchive ia(ss_value);
            ia >> p;
        }
    
    }
    

    Prints

    00 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00