C++ with Boost

Boost를 이용한 객체 직렬화(Object Serialization)

basker 2020. 7. 11. 16:12

객체 직렬화

객체 직렬화는 객체를 전송가능한 형태의 연속적인 데이터로 변형하는 것으로 파일로 저장하거나 전송할 때 사용된다.  객체를 연속적인 데이터로 변환하는 과정을 직렬화 연속적인 데이터를 객체로 변환하는 과정을 역직렬화라고 한다. 자바나 C# 은 객체 직렬화를 언어 자체에서 지원하지만 C++은 이를 지원하지 않는다.여기서는 boost의 serialization 컴포넌트를 사용해 직렬화를 구현하는 방법을 살펴본다.

 

Boost 의 객체 직렬화 

boost::text_oarchive는 객체를 받아 직렬화를 수행한 후 출력 스트림 객체에 전달한다. 반대로 text_iarchive 는 입력 스트림으로 직렬화된 데이터를 받아 객체로 변환한다.  

Boost를 이용한 객체 직렬화 

부스트의 텍스트 입출력 아카이브(text_oarchive, text_iarchive)가 모든 객체를 직렬화하지는 않는다.  해당 객체가 직렬화 가능하려면 아래와 같이 클래스에 템플릿 함수 serialize()를 구현하고 이를 사용하는 boost::serialization::access 클래스가 이 함수에 접근할 수 있도록 해당 클래스를 friend로 선언해 주어야 한다. 

class Serializable {
private:
    friend class boost::serialization::access;
    
    template<typename Archive>
    void serialize(Archive& ar, const unsigned int version);
};

template <typename Archive>
void Serializable::serialize(Archive& ar, const unsigned int version) {
    //모든 멤버 변수를 Archive ar의 & 연산자의 인자로 전달 
    ar & 멤버 변수 ;
}

위와 같이 정의된 객체를 직렬화하기 위해서는

1. 출력 스트림 객체를 boost::arcivie::text_oarchive에 참조로 전달하고 

2. text_oarchive 객체의 << 연산의 인자로 직렬화할 객체를 넘겨 준다. 

//직렬화를 위한 출력 스트링스트림 객체 
std::ostringstream oss;
//직렬화 수행을 위한 text_oarchive 직렬화결과는 oss에 데이터 스트림
boost::archive::text_oarchive oa(oss);
//직렬화를 위해 객체를 text_oarchive 의 입력으로 전달 
oa << c;

직렬화된 객체를 역직렬화하기 위해서는 

1. 역직렬화를 위한 입력 데이터 스트림 객체를 text_iarchive 에 넘겨주고 

2. text_iarchive 객체의  >> 연산을 통해 역직렬화 한다.

//역직렬화를 위한 입력 데이터 스트림 생성 (생성 시 직렬화된 데이터 스트림 전달)
std::istringstream iss(oss.str());
//text_iarchive 생성 시 역직렬화를 위한 입력 데이터 스트림 지정 
boost::archive::text_iarchive ia(iss);
Serializable result;
//text_iarchive >> 연산자를 이용해 역직렬화를 통한 객체 생성 
ia >> result;

 

위와같이 Boost를 사용해 직렬화를 구현시 역직렬화 가능한 객체를 생성하는 방법과 text_oarchive, text_iarchive를 이용해 직렬화와 역직렬화를 수행할 수 있어야 한다.

아래는 Contact 객체를 파일에 직렬화하고 직렬화된 파일을 읽어와 Contact 객체를 역직렬화한 전체 예제 파일이다.

  

Address.h

#pragma once
#include <iostream>
#include <boost/serialization/access.hpp>

class Address {
public:
    Address() = default;
    Address(std::string street, std::string city, std::string post_code, int suite);
    friend std::ostream& operator << (std::ostream& os, const Address& rhs);

private:
    std::string street, city, post_code;
    int suite;

private:
    friend boost::serialization::access;
    template<typename Archive>
    void serialize(Archive& ar, const unsigned int version);
};

//해당 멤버 변수에 대해 아카이브에 & 연산 수행 
template<typename Archive>
void Address::serialize(Archive& ar, const unsigned int version) {
    ar & street;
    ar & city;
    ar & post_code;
    ar & suite;
}

Address::Address(std::string street, std::string city, std::string post_code, int suite)
        :street{std::move(street)},city{std::move(city)}, post_code{std::move(post_code)},
         suite{suite} {}

std::ostream& operator << (std::ostream& os, const Address& rhs) {
    return os << rhs.street << ", " << rhs.city << "," << rhs.post_code << ", "
              << rhs.suite;
}

 

Contact.h

#pragma once
#include <iostream>
#include <boost/serialization/serialization.hpp>
#include "Address.h"

class Contact {
public:
    Contact() = default;
    Contact(std::string name, std::unique_ptr<Address> address);

private:
    std::string name;
    //std::unique_ptr 직렬화 지원
    std::unique_ptr<Address> address;
    friend std::ostream& operator << (std::ostream& os, const Contact& rhs);

private:
    friend class boost::serialization::access;
    template <typename Archive>
    void serialize(Archive& ar, const unsigned int version);

    friend class PersistenceManager;
};


Contact::Contact(std::string name, std::unique_ptr<Address> address)
:name{std::move(name)}, address{std::move(address)} {}

std::ostream& operator << (std::ostream& os, const Contact& rhs) {
    return os << rhs.name << '\n' << *rhs.address;
}

template <typename Archive>
void Contact::serialize(Archive& ar, const unsigned int version) {
    ar & name;
    ar & address;
}

PersistenceManager.h

#include <iostream>
#include <fstream>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/unique_ptr.hpp>
#include "Contact.h"

//SRP(Single Responsibility Principle)을 지키기 위해 Contact 객체의 로드와 저장은 
//PersistenceManger로 분리 
struct PersistenceManager {
    static void save(const Contact& c) {
    	//직렬화를 위한 파일스트림 생성 
        std::ofstream ofs(c.name + ".bin");
        //text_oaarchive에 출력 파일 스트림 지정 
        boost::archive::text_oarchive oa(ofs);
        // 직렬화 수행 
        oa << c;
    }

    static Contact load(const std::string& name) {
    	//역직렬화를 위한 파일 스트림 생성 
        std::ifstream ifs(name + ".bin");
        //text_iarchive에 역직렬화를 위한 입력 스트림 지정 
        boost::archive::text_iarchive ia(ifs);
        Contact result;
        //result로 역직렬화 수행 
        ia >> result;
        return result;
    }
};


main.cpp

#include <iostream>
#include "Contact.h"
#include "PersistenceManager.h"
using namespace std;
int main() {
  Contact nicki("nicki", std::make_unique<Address>("Jong-ro1", "Seoul", "03154", 1));
  //직렬화 
  PersistenceManager::save(nicki);
  //역직렬화 
  Contact person = PersistenceManager::load("nicki");
  std::cout << person << std::endl;

}

CMakefile.txt

cmake_minimum_required(VERSION 3.16)
project(contact)

set(CMAKE_CXX_STANDARD 14)
# boost 라이브러리 중 사용할 serialization 컴포넌트 지정
find_package(Boost 1.72 COMPONENTS serialization)
if(Boost_FOUND)
    # 부스트 헤더 파일 include
    include_directories(${Boost_INCLUDE_DIRS})
endif()


if(Boost_FOUND)
    add_executable(contact main.cpp Address.h Contact.h PersistenceManager.h)
    #boost serialization 컴포넌트 라이브러리 추가
    target_link_libraries(contact ${Boost_LIBRARIES})
endif()

 

빌드 및 실행 결과