Boost를 이용한 객체 직렬화(Object Serialization)
객체 직렬화
객체 직렬화는 객체를 전송가능한 형태의 연속적인 데이터로 변형하는 것으로 파일로 저장하거나 전송할 때 사용된다. 객체를 연속적인 데이터로 변환하는 과정을 직렬화 연속적인 데이터를 객체로 변환하는 과정을 역직렬화라고 한다. 자바나 C# 은 객체 직렬화를 언어 자체에서 지원하지만 C++은 이를 지원하지 않는다.여기서는 boost의 serialization 컴포넌트를 사용해 직렬화를 구현하는 방법을 살펴본다.
Boost 의 객체 직렬화
boost::text_oarchive는 객체를 받아 직렬화를 수행한 후 출력 스트림 객체에 전달한다. 반대로 text_iarchive 는 입력 스트림으로 직렬화된 데이터를 받아 객체로 변환한다.
부스트의 텍스트 입출력 아카이브(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()
빌드 및 실행 결과