2016-09-19 27 views
3

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

因此,我想编写一个类似于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; 

} 

失败。它给我的错误

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

为线

ar & Double_wrapper(value);

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

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

我在Ubuntu上使用boost版本1.58。


更新 的代码似乎在评论中提到用vc工作++。然而,在解释中this thread所作的发言似乎表明,它其实没有,因为

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.

如果我理解这个正确的,它不应该再保存,并试图反序列化后,关闭程序时工作一个新的实例。


更新 正如约翰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(增强1.54)一起运行时产生存档异常。我无法使用colxu上的rextester或g ++ 6.1.0上的clang 3.7进行测试,但由于(可能不相关)链接程序错误。

+0

为什么在Double_wrapper :: load(...)中使用const_cast。你的价值不是恒定的,所以从我的理解来看,这将是未定义的行为,编译器无法处理它。 static_cast的作品。 [只有const_cast可以用来转换(移除)constness](http://en.cppreference.com/w/cpp/language/const_cast) – lakeweb

+0

你是对的,谢谢。 Double_wrapper里面的值是const之前,我没有删除const_cast。我将删除问题中的const_cast。 但是,无论哪种方式,行为都不会改变。 – cero

+0

上述代码仍然存在相同的错误?它编译并运行良好,(在输出之后用oar.close()),在微软的V140上。 – lakeweb

回答

1

我想,而不是这样的:

ar & Double_wrapper(value); 

你应该这样做:

Double_wrapper wrapper(value); 
    ar & wrapper; 

原因是你的错误信息有关的事实抱怨它想要的是你使用的是右值左值。

+0

谢谢!这看起来很容易,足以满足我计划要做的事情。我意识到这个问题,但认为如果不将两个部分的序列化分开,我就无法改变它。 但是,我仍然不确定两点: 1.为什么它按照我预期的方式工作,例如: make_array或make_binary_object?有什么不同? 2.为什么这一切工作?反序列化不应该创建一个全新的对象吗?那么它如何保持参考?有没有关于这方面的规范? – cero

+0

另请注意,那时不需要宏BOOST_CLASS_IS_WRAPPER。我认为这个宏应该是我想使用包装的关键,但我不明白它是如何工作的(以及为什么不这样做)。 – cero

+0

嗨,约翰,我昨天在[Coliru](http://coliru.stacked-crooked.com/a/5efa428e8d12c537)上试了一下。但它使用gcc提升了性能。它与MS编译器一起工作。 Cero,它与你的编译器一起工作吗? – lakeweb