【问题标题】:How to write a wrapper for doubles to use with boost serialization?如何为双打编写一个包装器以与 boost 序列化一起使用?
【发布时间】:2016-09-19 10:04:05
【问题描述】:

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;

}

它失败了。它给了我错误

错误:类型的非常量引用的无效初始化 来自“Double_wrapper”类型的右值的“Double_wrapper&”

换行

ar & Double_wrapper(value);

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

任何帮助和/或解释将不胜感激!

我在 Ubuntu 上使用 boost 1.58 版。


更新 如 cmets 中所述,该代码似乎适用于 vc++。然而,解释this thread 中的陈述似乎表明它实际上没有,因为

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

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


更新 正如 John Zwinck 所建议的那样,通过替换调用可能会有一种解决方法

ar & Double_wrapper(value);

通过

Double_wrapper wrapper(value);
ar & wrapper;

但是,这似乎不是用于提升序列化的包装器对象的预期行为。此外,(我)不清楚这个解决方案是否稳定(我需要它与每个 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 对其进行测试。

【问题讨论】:

  • 你为什么在 Double_wrapper::load(...) 中使用 const_cast。您的值不是恒定的,因此据我了解,这将是未定义的行为,编译器无法处理。 static_cast 有效。 only const_cast may be used to cast away (remove) constness
  • 你说得对,谢谢。 Double_wrapper 内部的值之前是 const,我没有删除 const_cast。我将删除问题中的 const_cast。但是,无论哪种方式,行为都不会改变。
  • 上述代码仍然出现同样的错误?它在 Microsoft 的 V140 上编译并运行良好(输出后带有 oar.close( ))。
  • 是的,我愿意。这太有趣了。所以它可能是一个依赖于编译器的问题?到目前为止非常感谢您,我明天(这里是晚上 11.30)会再次检查它并发布我使用的编译器标志。在我读到的某个线程中,ld_library_path 可能存在问题,但直到现在我才忽略了这一点。也许我会看看这个。
  • 为了确保我们没有交叉,我完全复制了你现在拥有的内容,它编译时没有错误或警告,并且运行良好,使用 ofs.close();

标签: c++ serialization boost wrapper


【解决方案1】:

我认为不是这个:

    ar & Double_wrapper(value);

你应该这样做:

    Double_wrapper wrapper(value);
    ar & wrapper;

原因是您的错误消息抱怨您在需要左值的地方使用右值。

【讨论】:

  • 谢谢!这看起来很容易,足以满足我的计划。我意识到了这个问题,但认为如果不将序列化分为两部分,我就无法更改它。但是,我仍然不确定两点:1.为什么它可以按我的预期工作,例如make_array 还是 make_binary_object?有什么区别? 2. 为什么这会起作用?反序列化不应该创建一个全新的对象吗?那么它如何保存参考呢?有这方面的规范吗?
  • 另外请注意,此时不需要宏 BOOST_CLASS_IS_WRAPPER。我认为这个宏应该是我想使用包装器的关键,但我不明白它是如何工作的(以及为什么不这样做)。
  • 嗨,约翰,我昨天在 Coliru 上试过了。但它用 gcc 引起了轰动。它与 MS 编译器一起工作。 Cero,你的编译器能用吗?
  • 我没有买它。 Double_wrapper 是在堆栈上构造的,据我所知不是右值。所以为了确保我在类中添加了 ~Double_wrapper() 并设置了一个断点。是不是 gcc 在这里搞糊涂了?
  • 是的,这适用于我的编译器。不过,rextester 上没有 gcc 和 clang。我使用 g++ 5.4.0。
猜你喜欢
  • 1970-01-01
  • 2021-04-19
  • 2011-11-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-23
  • 2014-07-11
  • 1970-01-01
相关资源
最近更新 更多