Серийная версия производного класса без отслеживания классов в Boost (С++)
У меня есть некоторые проблемы с форсированием сериализации при сериализации производного класса с помощью указателя базового класса. Мне нужна система, которая сериализует некоторые объекты по мере их поступления в систему, поэтому мне нужно сериализоваться с течением времени. Это не проблема, так как я могу открыть boost::archive::binary_oarchive
и сериализовать объекты, когда это необходимо. Быстро я заметил, что boost выполнял отслеживание объектов по адресу памяти, поэтому первая проблема заключалась в том, что разные объекты во времени, которые используют один и тот же адрес памяти, были сохранены как один и тот же объект. Это можно устранить, используя следующий макрос в требуемом производном классе:
BOOST_CLASS_TRACKING(className, boost::serialization::track_never)
Это отлично работает, но опять же, когда базовый класс не абстрактный, базовый класс не сериализуется должным образом. В следующем примере метод сериализации базового класса вызывается только один раз с первым объектом. В следующем случае boost предполагает, что этот объект был сериализован раньше, хотя объект имеет другой тип.
#include <iostream>
#include <fstream>
#include <boost/serialization/export.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/list.hpp>
#include <boost/serialization/map.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/archive/archive_exception.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
using namespace std;
class AClass{
public:
AClass(){}
virtual ~AClass(){}
private:
double a;
double b;
//virtual void virtualMethod() = 0;
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & a;
ar & b;
cout << "A" << endl;
}
};
//BOOST_SERIALIZATION_ASSUME_ABSTRACT(Aclass)
//BOOST_CLASS_TRACKING(AClass, boost::serialization::track_never)
class BClass : public AClass{
public:
BClass(){}
virtual ~BClass(){}
private:
double c;
double d;
virtual void virtualMethod(){};
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & boost::serialization::base_object<AClass>(*this);
ar & c;
ar & d;
cout << "B" << endl;
}
};
// define export to be able to serialize through base class pointer
BOOST_CLASS_EXPORT(BClass)
BOOST_CLASS_TRACKING(BClass, boost::serialization::track_never)
class CClass : public AClass{
public:
CClass(){}
virtual ~CClass(){}
private:
double c;
double d;
virtual void virtualMethod(){};
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & boost::serialization::base_object<AClass>(*this);
ar & c;
ar & d;
cout << "C" << endl;
}
};
// define export to be able to serialize through base class pointer
BOOST_CLASS_EXPORT(CClass)
BOOST_CLASS_TRACKING(CClass, boost::serialization::track_never)
int main() {
cout << "Serializing...." << endl;
{
ofstream ofs("serialization.dat");
boost::archive::binary_oarchive oa(ofs);
for(int i=0;i<5;i++)
{
AClass* baseClassPointer = new BClass();
// serialize object through base pointer
oa << baseClassPointer;
// free the pointer so next allocation can reuse memory address
delete baseClassPointer;
}
for(int i=0;i<5;i++)
{
AClass* baseClassPointer = new CClass();
// serialize object through base pointer
oa << baseClassPointer;
// free the pointer so next allocation can reuse memory address
delete baseClassPointer;
}
}
getchar();
cout << "Deserializing..." << endl;
{
ifstream ifs("serialization.dat");
boost::archive::binary_iarchive ia(ifs);
try{
while(true){
AClass* a;
ia >> a;
delete a;
}
}catch(boost::archive::archive_exception const& e)
{
}
}
return 0;
}
При выполнении этой части кода результат следующий:
Serializing....
A
B
B
B
B
B
C
C
C
C
C
Deserializing...
A
B
B
B
B
B
C
C
C
C
C
Таким образом, базовый класс только сериализуется один раз, хотя в производном классе явно указан флаг track_never. Существует два разных способа решения этой проблемы. Первый заключается в том, чтобы абстрагироваться базовым классом с помощью чистого виртуального метода и вызвать макрос BOOST_SERIALIZATION_ASSUME_ABSTRACT(Aclass)
, а второй - поставить флаг track_never также в базовый класс (прокомментированный в коде).
Ни одно из этих решений не отвечает моим требованиям, так как я хочу делать в будущем пунктуальную сериализацию состояния системы, для чего потребуются функции отслеживания для данного DClass, расширяющего A (не B или C), а также AClass не должен быть абстрактным.
Любые подсказки? Есть ли способ явно вызвать метод сериализации базового класса, избегая функции отслеживания в базовом классе (который уже отключен в производном классе)?
Ответы
Ответ 1
После немного более пристального взгляда на boost:: serialization я также убежден, что для вашего запроса нет простого решения.
Как вы уже упоминали, поведение отслеживания для сериализации объявляется в классе по классам с BOOST_CLASS_TRACKING.
Эта глобальная информация const интерпретируется в процессе виртуального отслеживания методов из класса oserializer.
virtual bool tracking(const unsigned int /* flags */)
Поскольку это класс шаблонов, вы можете явно создать этот метод для своих классов.
namespace boost {
namespace archive {
namespace detail {
template<>
virtual bool oserializer<class binary_oarchive, class AClass >::tracking(const unsigned int f /* flags */) const {
return do_your_own_tracking_decision();
}
}}}
Теперь вы можете попробовать, например, иметь что-то вроде глобальной переменной и время от времени менять поведение отслеживания. (Например, в зависимости от того, какой класс деривации записывается в архив.)
Это похоже на "Сериализация", но "Deserializing", чем исключение.
Причиной этого является то, что состояние "отслеживания" для каждого класса записывается только в архив. Поэтому deserialize всегда ожидает данные для AClass, если считывается BClass или CClass (при аренде, если первая попытка записи для ACLL была отключена при отключении отслеживания).
Одним из возможных решений может быть использование параметра flags в методе tracking().
Этот параметр представляет флаги, созданные архивом, по умолчанию "0".
binary_oarchive(std::ostream & os, unsigned int flags = 0)
Флаги архива объявляются в basic_archive.hpp
enum archive_flags {
no_header = 1, // suppress archive header info
no_codecvt = 2, // suppress alteration of codecvt facet
no_xml_tag_checking = 4, // suppress checking of xml tags
no_tracking = 8, // suppress ALL tracking
flags_last = 8
};
no_tracking в настоящее время не поддерживается, но теперь вы можете добавить это поведение в отслеживание.
template<>
virtual bool oserializer<class binary_oarchive, class AClass >::tracking(const unsigned int f /* flags */) const {
return !(f & no_tracking);
}
Теперь вы можете взять в аренду решение для разных архивов, следует ли отслеживать AClass или нет.
boost::archive::binary_oarchive oa_nt(ofs, boost::archive::archive_flags::no_tracking);
И это изменения в вашем примере.
int main() {
cout << "Serializing...." << endl;
{
ofstream ofs("serialization1.dat");
boost::archive::binary_oarchive oa_nt(ofs, boost::archive::archive_flags::no_tracking);
//boost::archive::binary_oarchive oa(ofs);
for(int i=0;i<5;i++)
{
AClass* baseClassPointer = new BClass();
// serialize object through base pointer
oa_nt << baseClassPointer;
// free the pointer so next allocation can reuse memory address
delete baseClassPointer;
}
ofstream ofs2("serialization2.dat");
boost::archive::binary_oarchive oa(ofs2);
//boost::archive::binary_oarchive oa(ofs);
for(int i=0;i<5;i++)
{
AClass* baseClassPointer = new CClass();
// serialize object through base pointer
oa << baseClassPointer;
// free the pointer so next allocation can reuse memory address
delete baseClassPointer;
}
}
getchar();
cout << "Deserializing..." << endl;
{
ifstream ifs("serialization1.dat");
boost::archive::binary_iarchive ia(ifs);
try{
while(true){
AClass* a;
ia >> a;
delete a;
}
}catch(boost::archive::archive_exception const& e)
{
}
ifstream ifs2("serialization2.dat");
boost::archive::binary_iarchive ia2(ifs2);
try{
while(true){
AClass* a;
ia2 >> a;
delete a;
}
}catch(boost::archive::archive_exception const& e)
{
}
}
return 0;
}
namespace boost {
namespace archive {
namespace detail {
template<>
virtual bool oserializer<class binary_oarchive, class AClass >::tracking(const unsigned int f /* flags */) const {
return !(f & no_tracking);
}
}}}
Возможно, это не то, что вы ищете. Существует еще много методов, которые могут быть адаптированы с собственной реализацией. Или вам нужно создать собственный класс архивов.
Ответ 2
В конечном счете проблема заключается в том, что архив boost::serialization
представляет состояние в один момент времени, и вы хотите, чтобы ваш архив содержал состояние, которое изменилось, то есть указатели, которые были повторно использованы. Я не думаю, что есть простой флаг boost::serialization
, который вызывает поведение, которое вы хотите.
Однако, я думаю, что могут быть и другие обходные пути. Вы можете инкапсулировать сериализацию для класса в свой собственный архив, а затем архивировать инкапсуляцию. То есть вы можете реализовать сериализацию для B
следующим образом (обратите внимание, что вам нужно разделить serialize()
на save()
и load()
):
// #include <boost/serialization/split_member.hpp>
// #include <boost/serialization/string.hpp>
// Replace serialize() member function with this.
template<class Archive>
void save(Archive& ar, const unsigned int version) const {
// Serialize instance to a string (or other container).
// std::stringstream used here for simplicity. You can avoid
// some buffer copying with alternative stream classes that
// directly access an external container or iterator range.
std::ostringstream os;
boost::archive::binary_oarchive oa(os);
oa << boost::serialization::base_object<AClass>(*this);
oa << c;
oa << d;
// Archive string to top level.
const std::string s = os.str();
ar & s;
cout << "B" << endl;
}
template<class Archive>
void load(Archive& ar, const unsigned int version) {
// Unarchive string from top level.
std::string s;
ar & s;
// Deserialize instance from string.
std::istringstream is(s);
boost::archive::binary_iarchive ia(is);
ia >> boost::serialization::base_object<AClass>(*this);
ia >> c;
ia >> d;
cout << "B" << endl;
}
BOOST_SERIALIZATION_SPLIT_MEMBER()
Поскольку каждый экземпляр B
сериализуется в свой собственный архив, A
фактически не отслеживается, потому что в архиве B
имеется только одна ссылка. Это дает:
Serializing....
A
B
A
B
A
B
A
B
A
B
A
C
C
C
C
C
Deserializing...
A
B
A
B
A
B
A
B
A
B
A
C
C
C
C
C
Потенциальное возражение против этого метода - это накладные расходы на хранение инкапсуляции. Результат исходной тестовой программы составляет 319 байт, а модифицированная тестовая программа - 664 байта. Однако, если gzip применяется к обоим выходным файлам, тогда размеры составляют 113 байт для оригинала и 116 байтов для модификации. Если пространство вызывает беспокойство, я бы рекомендовал добавить сжатие к внешней сериализации, что можно легко сделать с помощью boost::iostreams
.
Еще одно возможное решение - продлить срок службы экземпляров до срока службы архива, чтобы указатели не использовались повторно. Это можно сделать, связав контейнер с экземпляром shared_ptr
с вашим архивом или выделив экземпляры из пула памяти.