简体   繁体   中英

How to write a wrapper for doubles to use with boost serialization?

The boost serialization library fails to handle special values for doubles correctly when using text archives. That is, trying to deserialize NaN, +inf or -inf will lead to an error (see eg this topic ).

Therefore I want to write a wrapper class/method, similar to make_array or make_binary_object (see boost doc ) to handle those values. I want to use it like this:

class MyClass {

public:
    double value;

    template <class Archive>
    void serialize(Archive &ar, const unsigned int){
        ar & Double_wrapper(value);
    }
};

However, I fail to understand how wrapper classes work internally. Especially I do not understand, how they manage to preserve the connection to the original variable (in this case value) when deserializing.

I tried to write the wrapper like this:

#include <boost/serialization/split_member.hpp>
#include <boost/serialization/wrapper.hpp>
#include <boost/serialization/tracking.hpp>
#include <limits>
#include <cmath>

class Double_wrapper {

private:
    enum double_type {DT_NONE, DT_NAN, DT_INF, DT_NINF};

    double& value;

public:
    Double_wrapper(double& val):value(val){}
    Double_wrapper(Double_wrapper const& rhs):value(rhs.value) {}

private:
    friend class boost::serialization::access;
    template<class Archive>
    void save(Archive & ar, const unsigned int) const {
        double_type flag = DT_NONE;
        double val = value;

        if (!std::isfinite(val)) {
            if (std::isnan(val))    {
                flag = DT_NAN;
            } else if (val > 0) {
                flag = DT_INF;
            } else {
                flag = DT_NINF;
            }
            val = 0;
        }

        ar & val;
        ar & flag;
    }
    template<class Archive>
    void load(Archive & ar, const unsigned int) const {
        double_type flag;

        ar & value;
        ar & flag;

        switch (flag) {
            case DT_NONE:   break;
            case DT_NAN:    value = std::numeric_limits<double>::quiet_NaN(); break;
            case DT_INF:    value = std::numeric_limits<double>::infinity(); break;
            case DT_NINF:   value = -std::numeric_limits<double>::infinity();
        }
    }

    BOOST_SERIALIZATION_SPLIT_MEMBER()
};

BOOST_CLASS_IS_WRAPPER(Double_wrapper)
BOOST_CLASS_TRACKING(Double_wrapper, boost::serialization::track_never)

However, this is more the result of a trial and error process, than of understanding how wrappers work. From this part of the doc I concluded, that I would need to declare the class as a wrapper. But it doesn't seem to work.

When I try to use above code with this MWE

#include <boost/serialization/split_member.hpp>
#include <boost/serialization/wrapper.hpp>
#include <boost/serialization/tracking.hpp>
#include <limits>
#include <cmath>

#include <boost/archive/tmpdir.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <fstream>
#include <iostream>

class Double_wrapper {

private:
    enum double_type {DT_NONE, DT_NAN, DT_INF, DT_NINF};

    double& value;

public:
    Double_wrapper(double& val):value(val){}
    Double_wrapper(Double_wrapper const& rhs):value(rhs.value) {}

private:
    friend class boost::serialization::access;
    template<class Archive>
    void save(Archive & ar, const unsigned int) const {
        double_type flag = DT_NONE;
        double val = value;

        if (!std::isfinite(val)) {
            if (std::isnan(val))    {
                flag = DT_NAN;
            } else if (val > 0) {
                flag = DT_INF;
            } else {
                flag = DT_NINF;
            }
            val = 0;
        }

        ar & val;
        ar & flag;
    }
    template<class Archive>
    void load(Archive & ar, const unsigned int) const {
        double_type flag;

        ar & value;
        ar & flag;

        switch (flag) {
            case DT_NONE:   break;
            case DT_NAN:    value = std::numeric_limits<double>::quiet_NaN(); break;
            case DT_INF:    value = std::numeric_limits<double>::infinity(); break;
            case DT_NINF:   value = -std::numeric_limits<double>::infinity();
        }
    }

    BOOST_SERIALIZATION_SPLIT_MEMBER()
};

BOOST_CLASS_IS_WRAPPER(Double_wrapper)
BOOST_CLASS_TRACKING(Double_wrapper, boost::serialization::track_never)

///////////////////////////////////////////////////////////////////////////////////////

class MyClass {

public:
    double value;

    template <class Archive>
    void serialize(Archive &ar, const unsigned int){
        ar & Double_wrapper(value);
    }
};

///////////////////////////////////////////////////////////////////////////////////////

int main() {

    MyClass tmp;
    tmp.value = std::numeric_limits<double>::quiet_NaN();

    std::cout << "value=" << tmp.value << std::endl;

    std::string filename(boost::archive::tmpdir());
    filename += "/tmp.txt";

    //Output
    std::ofstream ofs(filename.c_str(), std::ios_base::out);
    boost::archive::text_oarchive oar(ofs);
    oar << tmp;

    ofs.close();

    //Input
    MyClass newtmp;
    std::ifstream ifs(filename.c_str(), std::ios_base::in);
    boost::archive::text_iarchive iar(ifs);
    iar >> newtmp;

    std::cout << "value=" << newtmp.value << std::endl;

}

it fails. It gives me the error

error: invalid initialization of non-const reference of type 'Double_wrapper&' from an rvalue of type 'Double_wrapper'

for the line

ar & Double_wrapper(value);

So I am not sure what to do. It seems that using references doesn't work. Will pointers do the trick? Does this work at all?

Any help and/or explanation would be greatly appreciated!

I am using boost version 1.58 on Ubuntu.


Update The code seems to work with vc++ as mentioned in the comments. However, interpreting the statements made in this thread seems to suggest, that it in fact does not, since

The reason MSVC might have accepted it, nonetheless, could be because MSVC has an (evil) non-standard extension that extends lifetimes of temporaries, when bound to a non-const reference.

If I understand this correctly, it should not work anymore when shutting down the program after saving and trying to deserialize in a new instance.


Update As suggested by John Zwinck, there may be a workaround by replacing the call

ar & Double_wrapper(value);

by

Double_wrapper wrapper(value);
ar & wrapper;

However, this does not seem to be the intended behavior of a wrapper object for boost serialization. Moreover, it is unclear (to me) whether this solution is stable (I need it to run with every c++ compiler).

It seems to work on my computer with g++ 5.4.0 and clang++ 3.8.0. Furthermore it works with vc++ on Rextester (boost 1.6).

It produces an archive exception when run with g++ 4.9.3 on rextester (boost 1.54). I wasn't able to test it with clang 3.7 on rextester or g++ 6.1.0 on coliru yet due to (presumably unrelated) linker errors.

I think instead of this:

    ar & Double_wrapper(value);

You should do this:

    Double_wrapper wrapper(value);
    ar & wrapper;

Reason being that your error message complains about the fact that you're using an rvalue where it wants an lvalue.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM