Reflection이란 쉽게 말하면 프로그램이 Runtime 중에 자료형, 변수명, 데이터 값을 가져올 수 있는 기능이다.
이 Reflection을 이용하면 작업해야하는 코드량을 줄일 수 있다.
예를 들면 구조체를 만들어서 등록하면 UI가 자동으로 생성하게한다거나, Backend에서 내려주는 데이터를 자동으로 Parsing한다거나 등등 다양하게 활용이 가능하다.
참고 : https://learn.microsoft.com/ko-kr/cpp/dotnet/reflection-cpp-cli?view=msvc-170
리플렉션(C++/CLI)
자세한 정보: 리플렉션(C++/CLI)
learn.microsoft.com
다른 언어에서는 언어차원에서 기본으로 제공되고 있으나, C++에서는 현재 C++차기 버전에 추가된다고 한다.
https://isocpp.org/files/papers/P2996R4.html
Reflection for C++26
isocpp.org
하지만, 지금 당장 사용을 해야되기 때문에 라이브러리를 여러가지 조사를 해보았다.
1. RTTR
- git : https://github.com/rttrorg/rttr
GitHub - rttrorg/rttr: C++ Reflection Library
C++ Reflection Library. Contribute to rttrorg/rttr development by creating an account on GitHub.
github.com
이 라이브러리를 가장 처음에 접했다. 잘만들어져있고 개발할 때 잘 사용하였으나 치명적인 약점이 있다.
이 라이브러리는 DLL로만 빌드 및 배포가 가능하기 때문에 프로그램에 RTTR을 추가하게 되면 DLL도 같이 배포를 해야만 한다.
2. ENTT::META
- git : https://github.com/skypjack/entt
GitHub - skypjack/entt: Gaming meets modern C++ - a fast and reliable entity component system (ECS) and much more
Gaming meets modern C++ - a fast and reliable entity component system (ECS) and much more - skypjack/entt
github.com
이 라이브러리는 Header Only이다. 즉 Header만 추가해서 사용하면 모든 기능을 사용할 수 있다.
src/entt/meta 에 있는 헤더들을 이용해서 개발하면 된다.
이 라이브러리는 meta로 만들어졌다가 뒤에 entt 에 포함되면서 좀더 사용이 용이하게 개량되었다.
이 라이브러리의 사용법과 굉장히 유사하지만, stl container가 추가되었고, 좀 더 사용이 편하게 개량되었다고 보면된다.
https://github.com/skypjack/meta
GitHub - skypjack/meta: Header-only, non-intrusive and macro-free runtime reflection system in C++
Header-only, non-intrusive and macro-free runtime reflection system in C++ - skypjack/meta
github.com
예제 코드
#include <entt/meta/meta.hpp>
#include <entt/meta/factory.hpp>
#include <entt/meta/policy.hpp>
#include <entt/meta/container.hpp>
#include <string>
#include <vector>
#include <iostream>
#include <memory>
#include <map>
std::map<entt::id_type, std::string> gHasheNames;
#define REGISTER_TYPE(Type) \
gHasheNames[entt::hashed_string(typeid(Type).name()).value()] = typeid(Type).name(); \
entt::meta_factory<Type>().type(entt::hashed_string(typeid(Type).name()));
#define REGISTER_BASE(ChildType, ParentType) \
entt::meta_factory<ChildType>().base<ParentType>();
// 수정: entt::as_ref_t 정책 추가
#define REGISTER_DATA(Type, Data, Name) \
entt::meta_factory<Type>().data<&Type::Data, entt::as_ref_t>(entt::hashed_string(Name)); \
gHasheNames[entt::hashed_string(Name).value()] = Name;
// item 구조체 정의
struct item
{
int price;
std::string name;
};
// Person 구조체 정의
struct Person
{
int age; // 나이 (정수)
std::string name; // 이름 (문자열)
std::vector<std::string> hobbies; // 취미 (문자열 벡터)
std::vector<item> items; // 아이템 (item 구조체 벡터)
// 생성자 추가
Person(int age_, std::string name_, std::vector<std::string> hobbies_, std::vector<item> items_)
: age(age_), name(name_), hobbies(hobbies_), items(items_) {}
};
// Student 구조체 정의 (Person 상속)
struct Student : public Person
{
int grade; // 학년 (정수)
std::string school; // 학교 (문자열)
std::vector<std::string> subjects; // 과목 (문자열 벡터)
// 생성자 추가
Student(int age_, std::string name_, std::vector<std::string> hobbies_, std::vector<item> items_,
int grade_, std::string school_, std::vector<std::string> subjects_)
: Person(age_, name_, hobbies_, items_), grade(grade_), school(school_), subjects(subjects_) {}
};
// Teacher 구조체 정의 (Person 상속)
struct Teacher : public Person
{
int salary; // 연봉 (정수)
std::string subject; // 과목 (문자열)
std::vector<std::string> students; // 학생 (문자열 벡터)
// 생성자 추가
Teacher(int age_, std::string name_, std::vector<std::string> hobbies_, std::vector<item> items_,
int salary_, std::string subject_, std::vector<std::string> students_)
: Person(age_, name_, hobbies_, items_), salary(salary_), subject(subject_), students(students_) {}
};
int main()
{
// 구조체를 EnTT 리플렉션 시스템에 등록
REGISTER_TYPE(Person)
REGISTER_DATA(Person, age, "age")
REGISTER_DATA(Person, name, "name")
REGISTER_DATA(Person, hobbies, "hobbies")
REGISTER_DATA(Person, items, "items")
REGISTER_TYPE(Student)
REGISTER_BASE(Student, Person)
REGISTER_DATA(Student, grade, "grade")
REGISTER_DATA(Student, school, "school")
REGISTER_DATA(Student, subjects, "subjects")
REGISTER_TYPE(Teacher)
REGISTER_BASE(Teacher, Person)
REGISTER_DATA(Teacher, salary, "salary")
REGISTER_DATA(Teacher, subject, "subject")
REGISTER_DATA(Teacher, students, "students")
// Person, Student, Teacher 객체를 포함하는 벡터 생성
std::vector<std::unique_ptr<Person>> people;
people.push_back(std::make_unique<Person>(30, "kim", std::vector<std::string>{ "reading", "gaming" }, std::vector<item>{ {100, "sword"}, {200, "shield"} }));
people.push_back(std::make_unique<Student>(20, "lee", std::vector<std::string>{ "studying" }, std::vector<item>{}, 3, "harvard", std::vector<std::string>{ "physics", "chemistry" }));
people.push_back(std::make_unique<Teacher>(40, "park", std::vector<std::string>{ "teaching" }, std::vector<item>{}, 2000000, "math", std::vector<std::string>{ "alice", "bob" }));
// 각 객체의 모든 멤버를 출력하고 수정
for (auto& person_ptr : people)
{
// 객체를 meta_handle로 래핑하여 실제 타입 유지
entt::meta_handle meta_handle(person_ptr.get());
// 객체의 런타임 타입 가져오기
auto meta_type = meta_handle.type();
std::cout << "Type: " << meta_type.info().name() << std::endl;
// 모든 데이터 멤버 순회
for (auto member : meta_type.data())
{
// 멤버 이름 가져오기
std::string member_name = gHasheNames[member.first];
auto value = member.second.get(meta_handle);
// 타입별로 처리
if (value.type().id() == entt::resolve<int>().id()) {
int val = value.cast<int>();
std::cout << member_name << ": " << val << std::endl;
// 수정: 1 증가
member.second.set(meta_handle, entt::meta_any{ val + 1 });
std::cout << "Modified " << member_name << ": " << member.second.get(meta_handle).cast<int>() << std::endl;
}
else if (value.type().id() == entt::resolve<std::string>().id()) {
std::string val = value.cast<std::string>();
std::cout << member_name << ": " << val << std::endl;
// 수정: "_modified" 추가
auto& str_ref = value.cast<std::string&>();
str_ref += "_modified";
std::cout << "Modified " << member_name << ": " << member.second.get(meta_handle).cast<std::string>() << std::endl;
}
else if (value.type().id() == entt::resolve<std::vector<std::string>>().id()) {
auto vec_str = value.cast<std::vector<std::string>>();
std::cout << member_name << ": ";
for (const auto& str : vec_str) std::cout << str << " ";
std::cout << std::endl;
// 수정: "new_element" 추가
auto& vec_ref = value.cast<std::vector<std::string>&>();
vec_ref.push_back("new_element");
std::cout << "Modified " << member_name << ": ";
for (const auto& str : member.second.get(meta_handle).cast<std::vector<std::string>>()) std::cout << str << " ";
std::cout << std::endl;
}
else if (value.type().id() == entt::resolve<std::vector<item>>().id()) {
auto vec_item = value.cast<std::vector<item>>();
std::cout << member_name << ": ";
for (const auto& it : vec_item) std::cout << it.name << "(" << it.price << ") ";
std::cout << std::endl;
// 수정: 새로운 아이템 추가
auto& vec_ref = value.cast<std::vector<item>&>();
vec_ref.push_back({ 300, "new_item" });
std::cout << "Modified " << member_name << ": ";
for (const auto& it : member.second.get(meta_handle).cast<std::vector<item>>()) std::cout << it.name << "(" << it.price << ") ";
std::cout << std::endl;
}
else {
std::cout << member_name << ": Unknown type" << std::endl;
}
}
std::cout << "-----" << std::endl;
}
return 0;
}
'Basic Programming > C, C++' 카테고리의 다른 글
C/C++ - 정적 초기화 순서 실패 (0) | 2025.04.13 |
---|---|
C/C++ - QueueUserAPC (0) | 2023.04.20 |
C/C++ - Interrupt 감지 (0) | 2023.04.07 |
C/C++ - push_macro() (0) | 2022.10.12 |
C/C++ - vcpkg에서 MT, MD 변경 방법 (0) | 2022.02.17 |