-
c++에서 리플렉션 흉내내기프로그래밍/c++ 2018. 1. 3. 01:11반응형
원시 타입을 구분할 enum을 만든다.
enum class PrimitiveType{Char = 1,String,Int16,Int32,Int64,Real32,Real64,};멤버 변수 하나를 표현하는 클래스를 만든다.
class MemberVariable{public:MemberVariable(const char* name, PrimitiveType type, std::uint16_t offset) :name(name),type(type),offset(offset){}const char* GetName() const{return name.c_str();}PrimitiveType GetType() const{return type;}unsigned int GetOffset() const{return offset;}private:std::string name;PrimitiveType type;std::uint16_t offset;};변수의 집합을 표현하는 클래스를 만든다.
class DataType{public:template < typename Iterator >DataType(Iterator begin, Iterator end) :memberVariables(begin, end){}DataType(std::initializer_list<MemberVariable> memberVariables) :DataType(memberVariables.begin(), memberVariables.end()){}const std::vector<MemberVariable>& GetMemberVariables() const{return memberVariables;}private:std::vector<MemberVariable> memberVariables;};이제 DataType으로 클래스를 내부 구조를 표현할 수 있다.
Student란 클래스가 있다고 하자. 이 클래스는 이름, 나이, 다리 개수를 멤버 변수로 가진다.
class Student{public:std::string name = "Jeon";int age = 27;int legCount = 3;};여기에 내부 구조를 표현하도록 하면
class Student{public:std::string name = "Jeon";int age = 27;int legCount = 3;static const DataType& GetDataType(){static const DataType dataType{{ "name", PrimitiveType::String, offsetof(Student, name) },{ "age", PrimitiveType::Int32, offsetof(Student, age) },{ "legCount", PrimitiveType::Int32, offsetof(Student, legCount) },};return dataType;}};이렇게 표현 할 수 있겠다.
0. 리플렉션 활용해서 직렬화하기
개체를 메모리에 쓰는 헬퍼 클래스를 만든다.
#pragma once#include <vector>#include <cstddef>class OutputMemoryStream{public:OutputMemoryStream(){buffer.resize(32);}std::pair<const std::byte*, size_t> GetBufferData() const{return { buffer.data(), head };}const std::byte* GetBufferPointer() const{return buffer.data();}size_t GetBufferSize() const{return head;}void Write(const void* data, size_t size){Reserve(head + size);std::memcpy(buffer.data() + head, data, size);head = head + size;}template < typename Type >void Write(const Type& value){// 원시 자료형 여부 검사static_assert(std::is_arithmetic_v<Type> ||std::is_enum_v<Type>,"generic write only supports primitive data types");Write(&value, sizeof(value));}void Reserve(std::size_t size){buffer.reserve(head + size);}private:std::vector<std::byte> buffer;size_t head = 0;};핵심은 타입에 따라 메모리에 쓰는 방식을 구분하는 것이다.
메모리에 곧바로 쓰고 읽을 수 있는 int, float, char 같은 원시 타입이라면 곧바로 복사를 하지만 그게 아니라면 특수화된 처리가 필요하다.
template <>void Write<std::string>(const std::string& str){Write((std::uint32_t)str.length());Write(str.data(), str.length());}이런식으로.
void Serialize(OutputMemoryStream& stream, const DataType& dataType, const void* src){for (const auto& memberVariable : dataType.GetMemberVariables()){std::byte* p = (std::byte*)src + memberVariable.GetOffset();switch (memberVariable.GetType()){case PrimitiveType::Int32:stream.Write(*(std::int32_t*)p);break;case PrimitiveType::Int64:stream.Write(*(std::int64_t*)p);break;case PrimitiveType::Real32:stream.Write(*(std::float_t*)p);break;case PrimitiveType::Real64:stream.Write(*(std::double_t*)p);break;case PrimitiveType::String:stream.Write(*(std::string*)p);break;}}}이제 메모리에서 읽는 헬퍼 클래스를 만든다.
#pragma once#include <cstddef>#include <vector>class InputMemoryStream{public:InputMemoryStream(const void* data, std::size_t size);~InputMemoryStream();void Read(void* dest, std::size_t size);template < typename Type >void Read(Type& out){// 원시 자료형 여부 검사static_assert(std::is_arithmetic_v<Type> ||std::is_enum_v<Type>,"generic read only supports primitive data types");Read(&out, sizeof(Type));}template <>void Read<std::string>(std::string& out){std::uint32_t length;Read(length);std::string temp;temp.resize(length);Read(temp.data(), length);out = std::move(temp);}private:const void* data;std::size_t size;std::size_t index;};마찬가지로 원시 타입이라면 메모리 복사로 곧바로 읽지만 그게 아니라면 쓰기와 대칭되는 특수화된 읽기 방식이 필요하다.
-- 계속
반응형'프로그래밍 > c++' 카테고리의 다른 글
const std::string& 대신 std::string_view (0) 2018.02.20 bool은 왜 1비트가 아닌 1바이트인가 (0) 2018.02.09 asio 기반으로 라이브러리 프로토타입을 만들었다. (0) 2017.12.24 구글 플랫버퍼(flatbuffers) 맛보기 in c++ (1) 2017.11.30 c++로 텍스트 파일 쓸 때 참고 (0) 2017.06.15