2016-09-19 2 views
3

Библиотека с расширением сериализации не может правильно обрабатывать специальные значения для парных чисел при использовании текстовых архивов. То есть, попытка десериализации 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, но из-за (предположительно несвязанных) ошибок компоновщика.

+0

Почему вы используете const_cast в Double_wrapper :: load (...). Ваше значение не является постоянным, поэтому я понимаю, что это будет неопределенное поведение, компилятор не может справиться с этим. static_cast работает. [только const_cast может использоваться для удаления (удаления) константы] (http://en.cppreference.com/w/cpp/language/const_cast) – lakeweb

+0

Вы правы, спасибо. Значение внутри Double_wrapper было const до и я не удалял const_cast. Я удалю const_cast в вопросе. Однако в любом случае поведение не меняется. – cero

+0

Вы по-прежнему получаете ту же ошибку с вышеуказанным кодом? Он компилируется и работает нормально (с выходом oar.close() после вывода) на Microsoft V140. – lakeweb

ответ

1

Я думаю, что вместо этого:

ar & Double_wrapper(value); 

Вы должны сделать это:

Double_wrapper wrapper(value); 
    ar & wrapper; 

Причина в том, что ваше сообщение об ошибке жалуется на то, что вы используете RValue, где он хочет именующий.

+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

Смежные вопросы