C ++에서 클래스를 직렬화 및 역 직렬화 할 수 있습니까?


138

C ++에서 클래스를 직렬화 및 역 직렬화 할 수 있습니까?

나는 3 년 동안 Java를 사용해 왔으며 직렬화 / 역 직렬화는 그 언어에서 매우 사소한 것입니다. C ++에는 비슷한 기능이 있습니까? 직렬화를 처리하는 기본 라이브러리가 있습니까?

예가 도움이 될 것입니다.


2
"네이티브"가 무슨 뜻인지 확실하지 않다면, Boost.Serialization과 같은 네이티브 C ++을 의미합니까? C ++ 표준 라이브러리 만 사용한다는 의미입니까? 다른 의미가 있습니까?
jwfearn

1
"외부 소프트웨어 라이브러리가 아님"을 의미합니다. 그리고 미안하지만 내 영어는별로 좋지 않습니다 : S. 저는 아르헨티나 출신입니다
Agusti-N

3
객체를 직렬화하는 기본 방법은 없습니다 (여전히 POD에서 이진 데이터를 덤프 할 수는 있지만 원하는 것을 얻을 수는 없습니다). 여전히 Boost는 "내부 라이브러리"가 아니지만 컴파일러에 추가해야하는 첫 번째 외부 라이브러리입니다. 부스트는 STL 품질입니다 (예 : Top Gun C ++)
paercebal

답변:


95

Boost::serialization라이브러리는 오히려 우아하게이 문제를 처리합니다. 여러 프로젝트에서 사용했습니다. 사용 방법을 보여주는 예제 프로그램이 있습니다. .

이를 수행하는 유일한 기본 방법은 스트림을 사용하는 것입니다. 그것은 본질적으로 모든 Boost::serialization라이브러리가하는 것입니다. 텍스트와 같은 형식으로 객체를 작성하고 동일한 형식으로 읽도록 프레임 워크를 설정하여 스트림 방법을 확장합니다.

대한 유형, 또는 당신의 자신의 종류의 내장 operator<<operator>>제대로 매우 간단 그 정의; 자세한 내용 은 C ++ FAQ 를 참조하십시오.


boost :: serialization을 호출하면 객체를 쓰고 읽는 순서를 추적해야합니다. 그 맞습니까? 따라서 프로그램 버전간에 두 개의 필드가 작성되는 순서가 변경되면 호환되지 않습니다. 이게 옳은 거니?
Agnel Kurian

1
아마도 Boost :: serialization 코드 자체가 아니라 직렬화 기능 때문일 수 있습니다.
헤드 eek

1
@ 0xDEADBEEF : 아마도 이진 _ (i | o) 아카이브를 사용하면 엔디안과 같은 다른 "문제"가 발생합니다. text_ (i | o) archive를 시도하십시오. 플랫폼에 구애받지 않습니다.
Ela782

2
특정 프레임 워크 / 라이브러리 솔루션이 정답이되어서는 안됩니다.
Andrea

3
@Andrea : Boost 라이브러리는 특별한 경우입니다. C ++ 11이 완성 될 때까지는 최신 C ++ 코드를 작성하는 것이 불가능했지만 별도의 라이브러리보다 2 차 STL에 더 가깝습니다.
Geek

52

나는 이것이 오래된 게시물이라는 것을 알고 있지만 검색 할 때 가장 먼저 나온 것 중 하나입니다 c++ serialization.

C ++ 11에 액세스 할 수있는 사람이라면 누구나 바이너리, JSON 및 XML을 즉시 지원하는 직렬화를위한 C ++ 11 헤더 전용 라이브러리 인 cereal을 살펴 보는 것이 좋습니다 . 시리얼은 확장하고 사용하기 쉽게 설계되었으며 Boost와 비슷한 구문을 가지고 있습니다.


4
시리얼에 대한 좋은 점은 부스트와 달리 메타 데이터가 거의 없다는 것입니다 (거의 없음). boost :: serialization은 아카이브를 열 때마다 lib 버전을 스트림에 기록하므로 파일에 추가 할 수 없습니다.
CyberSnoopy

@CyberSnoopy-아카이브를 만들 때이 기능을 억제하는 플래그가 있습니다. 물론 아카이브를 읽을 때도 기억해야합니다.
Robert Ramey

16

부스트는 좋은 제안입니다. 그러나 자신만의 롤을 원한다면 그렇게 어렵지 않습니다.

기본적으로 객체 그래프를 작성한 다음 구조화 된 스토리지 형식 (JSON, XML, YAML 등)으로 출력하는 방법이 필요합니다. 그래프 재귀 괜찮은 객체 알고리즘을 사용하고 표시된 모든 객체를 출력하는 것처럼 간단하게 그래프를 작성합니다.

기초적인 (그러나 여전히 강력한) 직렬화 시스템을 설명하는 기사를 작성했습니다. 당신은 찾을 수 있습니다 그것은 흥미로운 : 온 디스크 파일 형식, 제 2 부로 SQLite는 사용 .


14

지금까지와 같은 "내장"라이브러리 이동합니다 <<>> 직렬화를 위해 특별히 예약되어있다.

<<객체를 직렬화 컨텍스트 (일반적으로 iostream) 로 출력 >>하고 해당 컨텍스트에서 데이터를 다시 읽으 려면 재정의해야합니다 . 각 개체는 집계 된 자식 개체를 출력합니다.

이 방법은 객체 그래프에 사이클이없는 한 제대로 작동합니다.

그렇다면, 그러한주기를 처리하기 위해 라이브러리를 사용해야합니다.


3
분명히, 그것은 옳은 일이 아닙니다 ... 구현 된 <<연산자는 사람이 읽을 수있는 객체의 텍스트 표현을 인쇄하는 데 사용되며, 이는 종종 직렬화에 원하지 않습니다.
einpoklum

1
@einpoklum <<generic 을 정의하는 대신 ostream파일 스트림에 대해이를 정의하십시오.
Carcigenicate

1
@Carcigenicate : 사람이 읽을 수있는 텍스트를 가져 오는 로그 파일은 파일 스트림입니다.
einpoklum

1
@einpoklum 무슨 말인지 잘 모르겠습니다. Frank의 말이 맞습니다.이 연산자를 사용하여 직렬화 할 수 있습니다. 방금 벡터를 직렬화 / 직렬화 해제하도록 정의했습니다.
Carcigenicate

2
캐치가 여기 있다고 생각합니다. " <<일련의 직렬화 컨텍스트로 객체를 출력 하려면 재정의해야합니다 . 각 객체는 출력을 담당합니다." — 문제는 각 객체에 대해 힘들게 작성하지 않아도되는 방법에 관한 것입니다. 언어 나 도서관이 도움이 되나요?
ShreevatsaR

14

Google 프로토콜 버퍼를 권장 합니다 . 새 프로젝트에서 라이브러리를 테스트 할 기회가 있었고 사용하기가 매우 쉽습니다. 라이브러리는 성능에 크게 최적화되어 있습니다.

Protobuf는 객체를 직렬화하지 않고 사양에 따라 직렬화되는 객체에 대한 코드를 생성한다는 점에서 여기에 언급 된 다른 직렬화 솔루션과 다릅니다.


2
이것을 사용하여 약 10-50MB 크기의 객체를 직렬화 한 경험이 있습니까? 문서는 프로토콜 버퍼가 크기가 약 MB 인 객체에 가장 적합하다고 말합니다.
Agnel Kurian

나는 내 라이브러리를 굴려서 스트림 (아직)을 사용하지 않기 때문에 실제로 작은 것들을위한 것입니다 : gist.github.com/earonesty/5ba3a93f391ea03ef90884840f068767
Erik Aronesty


4

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 용 코덱이 있으며 흥미로운 부분은 이름 값 쌍의 형태로 객체 클래스 표현을 유지할 수 있다는 것입니다.이 프로젝트에서 우연히 우연히 발견되었을 때 마지막 프로젝트에서 비슷한 프로토콜이 필요했습니다. 내 요구 사항에 따라 기본 라이브러리를 수정했습니다. 이것이 도움이되기를 바랍니다.




2

직렬화의 기초로 자주 사용되는 추상 팩토리를 살펴볼 것을 제안합니다.

C ++ 팩토리에 대한 또 다른 SO 질문에 대답했습니다. 유연한 공장에 관심 있다면 여기를 참조하십시오 . ET ++에서 오래된 매크로를 사용하여 나에게 큰 도움이되는 오래된 방법을 설명하려고합니다.

ET ++ 는 오래된 MacApp을 C ++ 및 X11로 이식하는 프로젝트였습니다. 그것의 노력으로 에릭 감마 (Eric Gamma) 등은 디자인 패턴 에 대해 생각하기 시작했다 . ET ++에는 런타임시 직렬화 및 내부 검사를위한 자동 방법이 포함되어 있습니다.


0

단순하고 최상의 성능을 원하고 이전 버전과의 데이터 호환성에 신경 쓰지 않으려면 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);

0

다음은 간단한 직렬 변환기 라이브러리입니다. 헤더 만 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
}

0

다음 템플릿을 사용하여 직렬화를 구현하고 있습니다.

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

추상 반복자 논리로 인해 반복자 (예 : 스트림 반복자), 포인터 등과 함께 작동해야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.