2013-03-25 11 views
15

기본 클래스 포인터를 통해 파생 클래스를 직렬화 할 때 부스트 직렬화에 몇 가지 문제가 있습니다. 시스템에서받은 객체를 직렬화하는 시스템이 필요하므로 시간이 지남에 따라 직렬화해야합니다. boost::archive::binary_oarchive을 열고 필요할 때 객체를 직렬화 할 수 있기 때문에 이것은 실제로 문제가되지 않습니다. 빠른 속도로 메모리 주소로 객체 추적을 수행하는 것이 보였으므로 첫 번째 문제는 동일한 객체를 동일한 메모리 주소로 공유하는 여러 객체가 동일한 객체로 저장된다는 점이었습니다. 이것은 필요한 파생 클래스에서 다음 매크로를 사용하여 고정 할 수 있습니다 : 기본 클래스가 추상적이 아닌 경우부스트 (C++)에서 클래스 추적없는 파생 클래스 직렬화

BOOST_CLASS_TRACKING(className, boost::serialization::track_never)

이는, 그러나 다시, 기본 클래스가 제대로 잘 직렬화되지 않은 작품. 다음 예제에서는 기본 클래스 serialization 메서드가 첫 번째 개체와 함께 한 번만 호출됩니다. 다음에서는 객체가 다른 유형이지만 이전에이 객체가 직렬화되었다고 가정합니다. 이 코드 부분을 실행하면

#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; 
} 

이 결과는 다음과 같다 : 파생 된 클래스가 명시 적으로 track_never 플래그이 있지만

Serializing.... 
A 
B 
B 
B 
B 
B 
C 
C 
C 
C 
C 

Deserializing... 
A 
B 
B 
B 
B 
B 
C 
C 
C 
C 
C 

가 기본 클래스는 한 번만 직렬화된다. 이 문제를 해결하기위한 두 가지 다른 해결 방법이 있습니다. 첫 번째는 순수 가상 메서드를 사용하여 기본 클래스를 추상화하고 매크로 BOOST_SERIALIZATION_ASSUME_ABSTRACT(Aclass)을 호출하는 것이며 두 번째는 기본 클래스 (코드에 주석 처리 됨)에 track_never 플래그를 넣는 것입니다.

시스템 상태의 향후 정시 직렬화에서 원하는 DLC (A 또는 B 또는 C가 아닌) 확장 기능을 필요로하므로이 솔루션 중 어느 것도 내 요구 사항을 충족시키지 못합니다. 또한 AClass는 추상적이 아니어야한다.

힌트가 있습니까? 명시 적으로 기본 클래스의 직렬화 메서드를 호출하여 기본 클래스의 추적 기능을 피하는 방법이 있습니까? (파생 클래스에서 이미 비활성화되어 있습니다.)

+0

boost가 A가 가상이 아니더라도 (즉,'BOOST_SERIALIZATION_ASSUME_ABSTRACT (Aclass)'줄의 주석 처리를 제거하고 컴파일하려고 시도한다고 가정)을 가정 할 수 있습니까? – Synxis

답변

2

부스트 :: 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(); 
    } 

}}} 

예를 들어 전역 변수와 같은 것을 사용하고 때때로 추적 동작을 변경할 수 있습니다. (어떤 파생 클래스가 아카이브에 쓰여지는지에 따라 다르다.) 이것은 "직렬화"를위한 것이지만 "직렬화 해제"는 예외를 던지기위한 것 같다. 이 이유는 각 클래스의 "추적"상태가 보관 파일에만 기록된다는 것입니다. 따라서 deserialize는 BClass 또는 CClass가 읽히는 경우 AClass에 대한 데이터를 항상 기대합니다 (AClass에 대한 첫 번째 쓰기 시도가 추적을 사용하지 않는 경우 임대).

하나의 가능한 솔루션은 tracking() 메소드에서 flags 매개 변수를 사용할 수 있습니다. 이 매개 변수는 아카이브가 생성되는 플래그를 나타내며, 기본값은 "0"입니다.

binary_oarchive(std::ostream & os, unsigned int flags = 0) 

는 보관 플래그는

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는 현재 지원되지 않는 것 같습니다 basic_archive.hpp에 선언되어있다,하지만 지금은 추적이 동작을 추가 할 수 있습니다.

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); 
    } 

}}} 

이것은 여전히 ​​찾고있는 것과 다를 수 있습니다. 자체 구현으로 적용 할 수있는 더 많은 메소드가 있습니다. 또는 자신의 아카이브 클래스를 파생시켜야합니다.

+0

좋은 접근 방법이 될 것 같습니다! 내 요구 사항을 충족시킬 수 있다고 생각합니다. 내일보다 자세한 내용을 확인하고 테스트 해 보겠습니다. 내가 다시 말해 줄께. 감사! –

+0

@alvarolb 나는 조금 궁금합니다. 너에게 효과가 있었 니? –

+0

마지막으로 나는 당신의 접근 방식을 테스트하는 데 약간의 시간이 걸리지 만 작동하지 않는 것 같습니다. oserializer 클래스 범위 안에 있지 않으므로 가상 추적 메서드에서 오류가 발생합니다. "가상 외부 클래스 선언". 그리고 부스트 라이브러리를 수정할 수 없습니다 : ( –

2

궁극적으로 문제는 boost::serialization 아카이브가 단일 시점에서 상태를 나타내며 아카이브가 변경된 상태, 즉 재사용 된 포인터를 포함하게하려는 것입니다. 나는 당신이 원하는 행동을 유도하는 간단한 boost::serialization 깃발이 있다고 생각하지 않는다.

그러나 충분할 수있는 다른 해결 방법이 있다고 생각합니다. 한 클래스에 대한 직렬화를 자체 아카이브에 캡슐화 한 다음 캡슐화를 보관할 수 있습니다. 즉, 당신이 (당신이 save()load()serialize()을 분할 할 필요가 있습니다)이 같은 B에 대한 직렬화를 구현할 수 있습니다 : B의 각 인스턴스는 자신의 아카이브로 직렬화하기 때문에

// #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() 

, 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 인스턴스의 컨테이너를 연결하거나 메모리 풀에서 인스턴스를 할당하여이 작업을 수행 할 수 있습니다.

+0

아카이브 캡슐화의 저장소 오버 헤드에 대한 참고 사항을 추가했습니다. – rhashimoto

+0

고마워요.하지만이 솔루션은 직렬화 프로세스를 복잡하게하고 크기를 늘립니다. 시스템이 무기한으로 작동하기 때문에 포인터의 수명을 연장 할 수도 없습니다. 어쨌든 도와 주셔서 감사합니다! –