Библиотека с расширением сериализации не может правильно обрабатывать специальные значения для парных чисел при использовании текстовых архивов. То есть, попытка десериализации 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;
}
он терпит неудачу. Это дает мне ошибку
error: invalid initialization of non-const reference of type ‘Double_wrapper&’ from an rvalue of type ‘Double_wrapper’
для линии
ar & Double_wrapper(value);
Так что я не знаю, что делать. Кажется, что использование ссылок не работает. Будут ли указатели делать трюк? Это вообще работает?
Любая помощь и/или объяснение были бы весьма признательны!
Я использую boost версию 1.58 на Ubuntu.
Update код, кажется, работает с 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.
Если я понимаю правильно, то он не должен работать больше, когда закрытие программы после сохранения и пытается десериализации в новый экземпляр.
Update Как предложил Джон Zwinck, может быть обходной путь, заменив вызов
ar & Double_wrapper(value);
по
Double_wrapper wrapper(value);
ar & wrapper;
Однако, это не кажется, предназначенное поведение объекта-обертки для повышения сериализации. Более того, неясно (для меня), является ли это решение стабильным (мне нужно, чтобы он работал с каждым компилятором C++).
Кажется, что работает на моем компьютере с g ++ 5.4.0 и clang ++ 3.8.0. Кроме того, он работает с vc++ on Rextester (boost 1.6).
Он создает исключение для архива при запуске с g++ 4.9.3 on rextester (boost 1.54). Я не смог проверить его с помощью clang 3.7 на rextester или g ++ 6.1.0 на coliru, но из-за (предположительно несвязанных) ошибок компоновщика.
Почему вы используете const_cast в Double_wrapper :: load (...). Ваше значение не является постоянным, поэтому я понимаю, что это будет неопределенное поведение, компилятор не может справиться с этим. static_cast работает. [только const_cast может использоваться для удаления (удаления) константы] (http://en.cppreference.com/w/cpp/language/const_cast) – lakeweb
Вы правы, спасибо. Значение внутри Double_wrapper было const до и я не удалял const_cast. Я удалю const_cast в вопросе. Однако в любом случае поведение не меняется. – cero
Вы по-прежнему получаете ту же ошибку с вышеуказанным кодом? Он компилируется и работает нормально (с выходом oar.close() после вывода) на Microsoft V140. – lakeweb