使用文本存档时,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进行测试,但由于(可能不相关)链接程序错误。
为什么在Double_wrapper :: load(...)中使用const_cast。你的价值不是恒定的,所以从我的理解来看,这将是未定义的行为,编译器无法处理它。 static_cast的作品。 [只有const_cast可以用来转换(移除)constness](http://en.cppreference.com/w/cpp/language/const_cast) – lakeweb
你是对的,谢谢。 Double_wrapper里面的值是const之前,我没有删除const_cast。我将删除问题中的const_cast。 但是,无论哪种方式,行为都不会改变。 – cero
上述代码仍然存在相同的错误?它编译并运行良好,(在输出之后用oar.close()),在微软的V140上。 – lakeweb