如何为 double 编写一个包装器以与 boost 序列化一起使用?
How to write a wrapper for doubles to use with boost serialization?
boost 序列化库在使用文本存档时无法正确处理双精度的特殊值。也就是说,尝试反序列化 NaN、+inf 或 -inf 将导致错误(参见 this topic)。
因此我想写一个包装器 class/method,类似于 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);
}
};
但是,我无法理解包装器 classes 在内部是如何工作的。特别是我不明白,他们如何在反序列化时设法保留与原始变量(在本例中为值)的连接。
我试过这样写包装器:
#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 我得出结论,我需要将 class 声明为包装器。但是好像不行。
当我尝试将上述代码与此 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);
所以我不知道该怎么做。似乎使用引用不起作用。指针能解决问题吗?这完全有效吗?
任何帮助 and/or 解释将不胜感激!
我在 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.
如果我理解正确,在保存并尝试在新实例中反序列化后关闭程序时它应该不再工作。
更新
正如 John Zwinck 所建议的那样,通过替换 call
可能有一个解决方法
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 对其进行测试。
我认为不是这样:
ar & Double_wrapper(value);
你应该这样做:
Double_wrapper wrapper(value);
ar & wrapper;
原因是您的错误消息抱怨您在需要左值的地方使用了右值。
boost 序列化库在使用文本存档时无法正确处理双精度的特殊值。也就是说,尝试反序列化 NaN、+inf 或 -inf 将导致错误(参见 this topic)。
因此我想写一个包装器 class/method,类似于 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);
}
};
但是,我无法理解包装器 classes 在内部是如何工作的。特别是我不明白,他们如何在反序列化时设法保留与原始变量(在本例中为值)的连接。
我试过这样写包装器:
#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 我得出结论,我需要将 class 声明为包装器。但是好像不行。
当我尝试将上述代码与此 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);
所以我不知道该怎么做。似乎使用引用不起作用。指针能解决问题吗?这完全有效吗?
任何帮助 and/or 解释将不胜感激!
我在 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.
如果我理解正确,在保存并尝试在新实例中反序列化后关闭程序时它应该不再工作。
更新 正如 John Zwinck 所建议的那样,通过替换 call
可能有一个解决方法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 对其进行测试。
我认为不是这样:
ar & Double_wrapper(value);
你应该这样做:
Double_wrapper wrapper(value);
ar & wrapper;
原因是您的错误消息抱怨您在需要左值的地方使用了右值。