프로그래밍/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;
};

마찬가지로 원시 타입이라면 메모리 복사로 곧바로 읽지만 그게 아니라면 쓰기와 대칭되는 특수화된 읽기 방식이 필요하다.


-- 계속

반응형