C ++에서 클래스를 직렬화 및 역 직렬화 할 수 있습니까?
나는 3 년 동안 Java를 사용해 왔으며 직렬화 / 역 직렬화는 그 언어에서 매우 사소한 것입니다. C ++에는 비슷한 기능이 있습니까? 직렬화를 처리하는 기본 라이브러리가 있습니까?
예가 도움이 될 것입니다.
C ++에서 클래스를 직렬화 및 역 직렬화 할 수 있습니까?
나는 3 년 동안 Java를 사용해 왔으며 직렬화 / 역 직렬화는 그 언어에서 매우 사소한 것입니다. C ++에는 비슷한 기능이 있습니까? 직렬화를 처리하는 기본 라이브러리가 있습니까?
예가 도움이 될 것입니다.
답변:
Boost::serialization
라이브러리는 오히려 우아하게이 문제를 처리합니다. 여러 프로젝트에서 사용했습니다. 사용 방법을 보여주는 예제 프로그램이 있습니다. .
이를 수행하는 유일한 기본 방법은 스트림을 사용하는 것입니다. 그것은 본질적으로 모든 Boost::serialization
라이브러리가하는 것입니다. 텍스트와 같은 형식으로 객체를 작성하고 동일한 형식으로 읽도록 프레임 워크를 설정하여 스트림 방법을 확장합니다.
대한 유형, 또는 당신의 자신의 종류의 내장 operator<<
과 operator>>
제대로 매우 간단 그 정의; 자세한 내용 은 C ++ FAQ 를 참조하십시오.
나는 이것이 오래된 게시물이라는 것을 알고 있지만 검색 할 때 가장 먼저 나온 것 중 하나입니다 c++ serialization
.
C ++ 11에 액세스 할 수있는 사람이라면 누구나 바이너리, JSON 및 XML을 즉시 지원하는 직렬화를위한 C ++ 11 헤더 전용 라이브러리 인 cereal을 살펴 보는 것이 좋습니다 . 시리얼은 확장하고 사용하기 쉽게 설계되었으며 Boost와 비슷한 구문을 가지고 있습니다.
부스트는 좋은 제안입니다. 그러나 자신만의 롤을 원한다면 그렇게 어렵지 않습니다.
기본적으로 객체 그래프를 작성한 다음 구조화 된 스토리지 형식 (JSON, XML, YAML 등)으로 출력하는 방법이 필요합니다. 그래프 재귀 괜찮은 객체 알고리즘을 사용하고 표시된 모든 객체를 출력하는 것처럼 간단하게 그래프를 작성합니다.
기초적인 (그러나 여전히 강력한) 직렬화 시스템을 설명하는 기사를 작성했습니다. 당신은 찾을 수 있습니다 그것은 흥미로운 : 온 디스크 파일 형식, 제 2 부로 SQLite는 사용 .
지금까지와 같은 "내장"라이브러리 이동합니다 <<
및>>
직렬화를 위해 특별히 예약되어있다.
<<
객체를 직렬화 컨텍스트 (일반적으로 iostream
) 로 출력 >>
하고 해당 컨텍스트에서 데이터를 다시 읽으 려면 재정의해야합니다 . 각 개체는 집계 된 자식 개체를 출력합니다.
이 방법은 객체 그래프에 사이클이없는 한 제대로 작동합니다.
그렇다면, 그러한주기를 처리하기 위해 라이브러리를 사용해야합니다.
<<
연산자는 사람이 읽을 수있는 객체의 텍스트 표현을 인쇄하는 데 사용되며, 이는 종종 직렬화에 원하지 않습니다.
<<
generic 을 정의하는 대신 ostream
파일 스트림에 대해이를 정의하십시오.
<<
일련의 직렬화 컨텍스트로 객체를 출력 하려면 재정의해야합니다 . 각 객체는 출력을 담당합니다." — 문제는 각 객체에 대해 힘들게 작성하지 않아도되는 방법에 관한 것입니다. 언어 나 도서관이 도움이 되나요?
Google 프로토콜 버퍼를 권장 합니다 . 새 프로젝트에서 라이브러리를 테스트 할 기회가 있었고 사용하기가 매우 쉽습니다. 라이브러리는 성능에 크게 최적화되어 있습니다.
Protobuf는 객체를 직렬화하지 않고 사양에 따라 직렬화되는 객체에 대한 코드를 생성한다는 점에서 여기에 언급 된 다른 직렬화 솔루션과 다릅니다.
Boost :: serialization 은 훌륭한 옵션이지만, 훨씬 더 우아한 시리얼 이라는 새로운 프로젝트를 경험했습니다 ! 나는 그것을 조사하는 것이 좋습니다.
amef 프로토콜을 확인할 수 있습니다. amef 에서 C ++ 인코딩의 예는 다음과 같습니다.
//Create a new AMEF object
AMEFObject *object = new AMEFObject();
//Add a child string object
object->addPacket("This is the Automated Message Exchange Format Object property!!","adasd");
//Add a child integer object
object->addPacket(21213);
//Add a child boolean object
object->addPacket(true);
AMEFObject *object2 = new AMEFObject();
string j = "This is the property of a nested Automated Message Exchange Format Object";
object2->addPacket(j);
object2->addPacket(134123);
object2->addPacket(false);
//Add a child character object
object2->addPacket('d');
//Add a child AMEF Object
object->addPacket(object2);
//Encode the AMEF obejct
string str = new AMEFEncoder()->encode(object,false);
자바 디코딩은 다음과 같습니다.
string arr = amef encoded byte array value;
AMEFDecoder decoder = new AMEFDecoder()
AMEFObject object1 = AMEFDecoder.decode(arr,true);
프로토콜 구현에는 C ++과 Java 용 코덱이 있으며 흥미로운 부분은 이름 값 쌍의 형태로 객체 클래스 표현을 유지할 수 있다는 것입니다.이 프로젝트에서 우연히 우연히 발견되었을 때 마지막 프로젝트에서 비슷한 프로토콜이 필요했습니다. 내 요구 사항에 따라 기본 라이브러리를 수정했습니다. 이것이 도움이되기를 바랍니다.
다른 포스터에서 설명한대로 부스트 직렬화를 사용하는 것이 좋습니다. 다음은 부스트 자습서를 훌륭하게 보완하는 사용법에 대한 자세한 자습서입니다. http://www.ocoudert.com/blog/2011/07/09/a-practical-guide-to-c-serialization/
달콤한 지속 는 또 다른 하나입니다.
XML, JSON, Lua 및 이진 형식으로 스트림과 직렬화 할 수 있습니다.
직렬화의 기초로 자주 사용되는 추상 팩토리를 살펴볼 것을 제안합니다.
C ++ 팩토리에 대한 또 다른 SO 질문에 대답했습니다. 유연한 공장에 관심 이 있다면 여기를 참조하십시오 . ET ++에서 오래된 매크로를 사용하여 나에게 큰 도움이되는 오래된 방법을 설명하려고합니다.
ET ++ 는 오래된 MacApp을 C ++ 및 X11로 이식하는 프로젝트였습니다. 그것의 노력으로 에릭 감마 (Eric Gamma) 등은 디자인 패턴 에 대해 생각하기 시작했다 . ET ++에는 런타임시 직렬화 및 내부 검사를위한 자동 방법이 포함되어 있습니다.
단순하고 최상의 성능을 원하고 이전 버전과의 데이터 호환성에 신경 쓰지 않으려면 HPS를 사용해보십시오. 가벼우면서 Boost보다 훨씬 빠르며 Protobuf보다 사용하기가 훨씬 쉽습니다.
예:
std::vector<int> data({22, 333, -4444});
std::string serialized = hps::serialize_to_string(data);
auto parsed = hps::parse_from_string<std::vector<int>>(serialized);
다음은 간단한 직렬 변환기 라이브러리입니다. 헤더 만 c11이며 기본 유형을 직렬화하는 예제가 있습니다. 다음은 수업지도입니다.
https://github.com/goblinhack/simple-c-plus-plus-serializer
#include "c_plus_plus_serializer.h"
class Custom {
public:
int a;
std::string b;
std::vector c;
friend std::ostream& operator<<(std::ostream &out,
Bits my)
{
out << bits(my.t.a) << bits(my.t.b) << bits(my.t.c);
return (out);
}
friend std::istream& operator>>(std::istream &in,
Bits my)
{
in >> bits(my.t.a) >> bits(my.t.b) >> bits(my.t.c);
return (in);
}
friend std::ostream& operator<<(std::ostream &out,
class Custom &my)
{
out << "a:" << my.a << " b:" << my.b;
out << " c:[" << my.c.size() << " elems]:";
for (auto v : my.c) {
out << v << " ";
}
out << std::endl;
return (out);
}
};
static void save_map_key_string_value_custom (const std::string filename)
{
std::cout << "save to " << filename << std::endl;
std::ofstream out(filename, std::ios::binary );
std::map< std::string, class Custom > m;
auto c1 = Custom();
c1.a = 1;
c1.b = "hello";
std::initializer_list L1 = {"vec-elem1", "vec-elem2"};
std::vector l1(L1);
c1.c = l1;
auto c2 = Custom();
c2.a = 2;
c2.b = "there";
std::initializer_list L2 = {"vec-elem3", "vec-elem4"};
std::vector l2(L2);
c2.c = l2;
m.insert(std::make_pair(std::string("key1"), c1));
m.insert(std::make_pair(std::string("key2"), c2));
out << bits(m);
}
static void load_map_key_string_value_custom (const std::string filename)
{
std::cout << "read from " << filename << std::endl;
std::ifstream in(filename);
std::map< std::string, class Custom > m;
in >> bits(m);
std::cout << std::endl;
std::cout << "m = " << m.size() << " list-elems { " << std::endl;
for (auto i : m) {
std::cout << " [" << i.first << "] = " << i.second;
}
std::cout << "}" << std::endl;
}
void map_custom_class_example (void)
{
std::cout << "map key string, value class" << std::endl;
std::cout << "============================" << std::endl;
save_map_key_string_value_custom(std::string("map_of_custom_class.bin"));
load_map_key_string_value_custom(std::string("map_of_custom_class.bin"));
std::cout << std::endl;
}
산출:
map key string, value class
============================
save to map_of_custom_class.bin
read from map_of_custom_class.bin
m = 2 list-elems {
[key1] = a:1 b:hello c:[2 elems]:vec-elem1 vec-elem2
[key2] = a:2 b:there c:[2 elems]:vec-elem3 vec-elem4
}
다음 템플릿을 사용하여 직렬화를 구현하고 있습니다.
template <class T, class Mode = void> struct Serializer
{
template <class OutputCharIterator>
static void serializeImpl(const T &object, OutputCharIterator &&it)
{
object.template serializeThis<Mode>(it);
}
template <class InputCharIterator>
static T deserializeImpl(InputCharIterator &&it, InputCharIterator &&end)
{
return T::template deserializeFrom<Mode>(it, end);
}
};
template <class Mode = void, class T, class OutputCharIterator>
void serialize(const T &object, OutputCharIterator &&it)
{
Serializer<T, Mode>::serializeImpl(object, it);
}
template <class T, class Mode = void, class InputCharIterator>
T deserialize(InputCharIterator &&it, InputCharIterator &&end)
{
return Serializer<T, Mode>::deserializeImpl(it, end);
}
template <class Mode = void, class T, class InputCharIterator>
void deserialize(T &result, InputCharIterator &&it, InputCharIterator &&end)
{
result = Serializer<T, Mode>::deserializeImpl(it, end);
}
T
직렬화하려는 유형은 다음 과 같이 Mode
여러 종류의 직렬화를 구별하는 더미 유형입니다. 리틀 엔디안, 빅 엔디안, 버린 트 등으로 같은 정수를 직렬화 할 수 있습니다.
기본적으로 Serializer
작업을 직렬화되는 개체에 위임합니다. 내장 유형의 경우 템플릿을 특수화해야합니다 Serializer
.
편의 기능 템플릿도 제공됩니다.
예를 들어 부호없는 정수의 리틀 엔디안 직렬화 :
struct LittleEndianMode
{
};
template <class T>
struct Serializer<
T, std::enable_if_t<std::is_unsigned<T>::value, LittleEndianMode>>
{
template <class InputCharIterator>
static T deserializeImpl(InputCharIterator &&it, InputCharIterator &&end)
{
T res = 0;
for (size_t i = 0; i < sizeof(T); i++)
{
if (it == end) break;
res |= static_cast<T>(*it) << (CHAR_BIT * i);
it++;
}
return res;
}
template <class OutputCharIterator>
static void serializeImpl(T number, OutputCharIterator &&it)
{
for (size_t i = 0; i < sizeof(T); i++)
{
*it = (number >> (CHAR_BIT * i)) & 0xFF;
it++;
}
}
};
그런 다음 직렬화하십시오.
std::vector<char> serialized;
uint32_t val = 42;
serialize<LittleEndianMode>(val, std::back_inserter(serialized));
역 직렬화하려면 다음을 수행하십시오.
uint32_t val;
deserialize(val, serialized.begin(), serialized.end());
추상 반복자 논리로 인해 반복자 (예 : 스트림 반복자), 포인터 등과 함께 작동해야합니다.