如何编写双精度包装器以与boost序列化一起使用?

时间:2016-09-19 10:04:05

标签: c++ serialization boost wrapper

使用文本存档时,boost序列化库无法正确处理双精度的特殊值。也就是说,尝试反序列化NaN,+ inf或-inf会导致错误(请参阅例如this topic)。

因此,我想编写一个包装类/方法,类似于make_array或make_binary_object(请参阅boost doc)来处理这些值。我想这样使用它:

class MyClass {

public:
    double value;

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

但是,我无法理解包装器类如何在内部工作。特别是我不明白,他们在反序列化时如何设法保持与原始变量的连接(在本例中为值)。

我试着像这样编写包装器:

#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)

然而,这更多是试错过程的结果,而不是了解包装器的工作原理。从this part of the doc我得出结论,我需要将该类声明为包装器。但它似乎没有用。

当我尝试将上述代码用于此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;

}

失败了。它给了我错误

  

错误:类型的非const引用的初始化无效   来自'Double_wrapper'类型的右值的'Double_wrapper&amp;'

为行

  

ar&amp; Double_wrapper(值);

所以我不知道该怎么做。似乎使用引用不起作用。指针会起作用吗?这有用吗?

非常感谢任何帮助和/或解释!

我在Ubuntu上使用boost版本1.58。

更新 该代码似乎与评论中提到的vc ++一起使用。但是,解释this thread中所作的陈述似乎表明,它实际上并没有,因为

  

MSVC可能接受它的原因可能是因为MSVC有一个(邪恶的)非标准扩展,当绑定到非const引用时会扩展临时生命周期。

如果我理解正确,那么在保存并尝试在新实例中反序列化后关闭程序时,它应该不再起作用。

更新 正如John Zwinck所建议的那样,可能有一个替代呼叫

的解决方法
ar & Double_wrapper(value);

通过

Double_wrapper wrapper(value);
ar & wrapper;

但是,这似乎不是boost序列化的包装器对象的预期行为。此外,(我)还不清楚这个解决方案是否稳定(我需要它与每个c ++编译器一起运行)。

它似乎可以在我的电脑上使用g ++ 5.4.0和clang ++ 3.8.0。此外,它适用于vc++ on Rextester(增强1.6)。

使用g++ 4.9.3 on rextester(boost 1.54)运行时会产生存档异常。我无法使用rextester上的clang 3.7或coliru上的g ++ 6.1.0测试它,但由于(可能是无关的)链接器错误。

1 个答案:

答案 0 :(得分:1)

我认为不是这样:

    ar & Double_wrapper(value);

你应该这样做:

    Double_wrapper wrapper(value);
    ar & wrapper;

原因是您的错误消息抱怨您正在使用左值来获取左值。