diff --git a/iguana/pb_util.hpp b/iguana/pb_util.hpp index 71c5ae1a..b2e46772 100644 --- a/iguana/pb_util.hpp +++ b/iguana/pb_util.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -34,16 +35,15 @@ IGUANA_INLINE void to_pb(T& t, Stream& out); template IGUANA_INLINE void from_pb(T& t, std::string_view pb_str); -struct pb_base { - virtual void to_pb(std::string& str) {} - virtual void from_pb(std::string_view str) {} +using base = detail::base; - size_t cache_size = 0; - virtual ~pb_base() {} -}; +template +IGUANA_INLINE constexpr size_t member_offset(T* t, U T::*member) { + return (char*)&(t->*member) - (char*)t; +} template -struct pb_base_impl : public pb_base { +struct base_impl : public base { void to_pb(std::string& str) override { iguana::to_pb(*(static_cast(this)), str); } @@ -51,11 +51,61 @@ struct pb_base_impl : public pb_base { void from_pb(std::string_view str) override { iguana::from_pb(*(static_cast(this)), str); } - virtual ~pb_base_impl() {} + + iguana::detail::field_info get_field_info(std::string_view name) override { + static constexpr auto map = iguana::get_members(); + iguana::detail::field_info info{}; + for (auto [no, field] : map) { + if (info.offset > 0) { + break; + } + std::visit( + [&](auto val) { + if (val.field_name == name) { + info.offset = member_offset((T*)this, val.member_ptr); + using value_type = typename decltype(val)::value_type; +#if defined(__clang__) || defined(_MSC_VER) || \ + (defined(__GNUC__) && __GNUC__ > 8) + info.type_name = type_string(); +#endif + } + }, + field); + } + + return info; + } + + std::vector get_fields_name() override { + static constexpr auto map = iguana::get_members(); + std::vector vec; + for (auto [no, val] : map) { + std::visit( + [&](auto& field) { + vec.push_back(std::string_view(field.field_name.data(), + field.field_name.size())); + }, + val); + } + return vec; + } + + virtual ~base_impl() {} + + size_t cache_size = 0; }; template -constexpr bool inherits_from_pb_base_v = std::is_base_of_v; +constexpr bool inherits_from_base_v = std::is_base_of_v; + +IGUANA_INLINE std::shared_ptr create_instance(std::string_view name) { + auto it = iguana::detail::g_pb_map.find(name); + if (it == iguana::detail::g_pb_map.end()) { + throw std::invalid_argument(std::string(name) + + "not inheried from iguana::base_impl"); + } + return it->second(); +} namespace detail { template @@ -468,7 +518,7 @@ IGUANA_INLINE size_t pb_key_value_size(Type&& t, Arr& size_arr) { static constexpr auto tuple = get_members_tuple(); constexpr size_t SIZE = std::tuple_size_v>; size_t pre_index = -1; - if constexpr (!inherits_from_pb_base_v && key_size != 0) { + if constexpr (!inherits_from_base_v && key_size != 0) { pre_index = size_arr.size(); size_arr.push_back(0); // placeholder } @@ -497,7 +547,7 @@ IGUANA_INLINE size_t pb_key_value_size(Type&& t, Arr& size_arr) { } }, std::make_index_sequence{}); - if constexpr (inherits_from_pb_base_v) { + if constexpr (inherits_from_base_v) { t.cache_size = len; } else if constexpr (key_size != 0) { @@ -566,7 +616,7 @@ template IGUANA_INLINE size_t pb_value_size(Type&& t, uint32_t*& sz_ptr) { using T = std::remove_const_t>; if constexpr (is_reflection_v || is_custom_reflection_v) { - if constexpr (inherits_from_pb_base_v) { + if constexpr (inherits_from_base_v) { return t.cache_size; } else { diff --git a/iguana/reflection.hpp b/iguana/reflection.hpp index 28e1b961..fdefa2bc 100644 --- a/iguana/reflection.hpp +++ b/iguana/reflection.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -19,6 +20,7 @@ #include "detail/string_stream.hpp" #include "detail/traits.hpp" +#include "enum_reflection.hpp" #include "frozen/string.h" #include "frozen/unordered_map.h" @@ -553,7 +555,85 @@ namespace iguana::detail { template struct identity {}; +struct field_info { + size_t offset; + std::string_view type_name; +}; + +struct base { + virtual void to_pb(std::string &str) {} + virtual void from_pb(std::string_view str) {} + virtual std::vector get_fields_name() { return {}; } + virtual iguana::detail::field_info get_field_info(std::string_view name) { + return {}; + } + + template + T &get_field_value(std::string_view name) { + auto info = get_field_info(name); + check_field(name, info); + auto ptr = (((char *)this) + info.offset); + return *((T *)ptr); + } + + template + void set_field_value(std::string_view name, T val) { + auto info = get_field_info(name); + check_field(name, info); + + auto ptr = (((char *)this) + info.offset); + + static_assert(std::is_constructible_v, "can not assign"); + + *((FiledType *)ptr) = std::move(val); + } + virtual ~base() {} + + private: + template + void check_field(std::string_view name, const field_info &info) { + if (info.offset == 0) { + throw std::invalid_argument(std::string(name) + " field not exist "); + } + +#if defined(__clang__) || defined(_MSC_VER) || \ + (defined(__GNUC__) && __GNUC__ > 8) + if (info.type_name != iguana::type_string()) { + std::string str = "type is not match: can not assign "; + str.append(iguana::type_string()); + str.append(" to ").append(info.type_name); + + throw std::invalid_argument(str); + } +#endif + } +}; + +inline std::unordered_map()>> + g_pb_map; + +template +inline bool register_type() { +#if defined(__clang__) || defined(_MSC_VER) || \ + (defined(__GNUC__) && __GNUC__ > 8) + if constexpr (std::is_base_of_v) { + auto it = g_pb_map.emplace(type_string(), [] { + return std::make_shared(); + }); + return it.second; + } + else { + return true; + } +#else + return true; +#endif +} + #define MAKE_META_DATA_IMPL(STRUCT_NAME, ...) \ + static inline bool IGUANA_UNIQUE_VARIABLE(reg_var) = \ + iguana::detail::register_type(); \ [[maybe_unused]] inline static auto iguana_reflect_members( \ const iguana::detail::identity &) { \ struct reflect_members { \ @@ -844,11 +924,7 @@ constexpr inline auto get_members() { STRUCT_NAME, ALIAS, \ std::tuple_size_v, __VA_ARGS__) -#ifdef _MSC_VER #define IGUANA_UNIQUE_VARIABLE(str) MACRO_CONCAT(str, __COUNTER__) -#else -#define IGUANA_UNIQUE_VARIABLE(str) MACRO_CONCAT(str, __LINE__) -#endif template struct iguana_required_struct; #define REQUIRED_IMPL(STRUCT_NAME, N, ...) \ @@ -903,12 +979,6 @@ inline int add_custom_fields(std::string_view key, return 0; } -#ifdef _MSC_VER -#define IGUANA_UNIQUE_VARIABLE(str) MACRO_CONCAT(str, __COUNTER__) -#else -#define IGUANA_UNIQUE_VARIABLE(str) MACRO_CONCAT(str, __LINE__) -#endif - #define CUSTOM_FIELDS_IMPL(STRUCT_NAME, N, ...) \ inline auto IGUANA_UNIQUE_VARIABLE(STRUCT_NAME) = iguana::add_custom_fields( \ #STRUCT_NAME, {MARCO_EXPAND(MACRO_CONCAT(CON_STR, N)(__VA_ARGS__))}); @@ -953,7 +1023,7 @@ struct is_reflection>> template inline auto iguana_reflect_type(const T &t) { - if constexpr (is_public_reflection_v) { + if constexpr (is_public_reflection_v>) { return iguana_reflect_members(iguana::detail::identity{}); } else { diff --git a/lang/struct_pb_intro.md b/lang/struct_pb_intro.md index 2e61bdc8..233d8bbf 100644 --- a/lang/struct_pb_intro.md +++ b/lang/struct_pb_intro.md @@ -11,14 +11,14 @@ struct_pb 是基于C++17 开发的高性能、易用、header only的protobuf格 ```cpp #include -struct my_struct : struct_pb::pb_base_impl { +struct my_struct { int x; bool y; struct_pb::fixed64_t z; }; REFLECTION(my_struct, x, y, z); -struct nest : struct_pb::pb_base_impl { +struct nest { std::string name; my_struct value; int var; @@ -29,10 +29,10 @@ REFLECTION(nest, name, value, var); ### 序列化 ```cpp int main() { - nest v{0, "Hi", {0, 1, false, 3}, 5}, v2{}; + nest v{"Hi", {1, false, {3}}, 5}, v2{}; std::string s; - struct_pb::to_pb(v, s); - struct_pb::from_pb(v2, s); + iguana::to_pb(v, s); + iguana::from_pb(v2, s); assert(v.var == v2.var); assert(v.value.y == v2.value.y); assert(v.value.z == v2.value.z); @@ -53,6 +53,68 @@ message nest { } ``` +## 动态反射 +特性: +- 根据对象名称创建实例; +- 获取对象的所有字段名; +- 根据对象实例和字段名获取或设置字段的值 + +### 根据名称创建对象 +```cpp +struct my_struct { + int x; + bool y; + iguana::fixed64_t z; +}; +REFLECTION(my_struct, x, y, z); + +struct nest1 : public iguana::base_imple { + nest1() = default; + nest1(std::string s, my_struct t, int d) + : name(std::move(s)), value(t), var(d) {} + std::string name; + my_struct value; + int var; +}; +REFLECTION(nest1, name, value, var); +``` + +```cpp +std::shared_ptr t = iguana::create_instance("nest1"); +``` +根据对象nest1创建了实例,返回的是基类指针。 + +“根据对象名称创建实例” 要求对象必须从iguana::base_impl 派生,如果没有派生则创建实例会抛异常。 + +### 根据名称设置字段的值 +```cpp + auto t = iguana::create_instance("nest1"); + + std::vector fields_name = t->get_fields_name(); + CHECK(fields_name == std::vector{"name", "value", "var"}); + + my_struct mt{2, true, {42}}; + t->set_field_value("value", mt); + t->set_field_value("name", std::string("test")); + t->set_field_value("var", 41); + nest1 *st = dynamic_cast(t.get()); + auto p = *st; + std::cout << p.name << "\n"; + auto &r0 = t->get_field_value("name"); + CHECK(r0 == "test"); + auto &r = t->get_field_value("var"); + CHECK(r == 41); + auto &r1 = t->get_field_value("value"); + CHECK(r1.x == 2); +``` +“根据对象实例和字段名获取或设置字段的值” 如果字段名不存在则会抛异常;如果设置的值类型和结构体字段类型不相同则会抛异常;需要类型完全一样,不允许隐式转换。比如字段类型是double,但是设置字段的值类型是int也会抛异常,必须显式传double;如果字段类型是std::string, 设置值类型是const char * 同样会报错;如果字段类型是int32_t, 设置值类型是uint_8也会抛异常,因为类型不相同。 + +设置字段值时也可以显式指定字段类型: +```cpp +t->set_field_value("name", "test"); +``` +这种方式则不要求设置值的类型和字段类型完全一样,只要能赋值成功即可。如果显式指定的字段类型不是实际的字段类型时也会抛异常。 + ## benchmark 在benchmark monster场景下,struct_pb 性能比protobuf 更好,序列化速度是protobuf的2.4倍,反序列化是protobuf的3.4倍。详情可以自行运行struct_pack 中的benchmark复现结果。 @@ -96,10 +158,10 @@ oneof -> `std::variant <...>` - 目前还只支持proto3,不支持proto2; - 目前还没支持反射; - 还没支持unkonwn字段; -- struct_pb 结构体必须派生于pb_base_impl +- struct_pb 结构体必须派生于base_impl ## roadmap - 支持proto2; - 支持反射; - 支持unkonwn字段; -- 去除struct_pb 结构体必须派生于pb_base_impl的约束; +- 去除struct_pb 结构体必须派生于base_impl的约束; diff --git a/test/proto/unittest_proto3.h b/test/proto/unittest_proto3.h index 594bd563..c1123b7f 100644 --- a/test/proto/unittest_proto3.h +++ b/test/proto/unittest_proto3.h @@ -7,7 +7,7 @@ #endif #define PB_CHECK assert -#define PUBLIC(T) : public iguana::pb_base_impl +#define PUBLIC(T) : public iguana::base_impl // define the struct as msg in proto namespace stpb { diff --git a/test/test_pb.cpp b/test/test_pb.cpp index 9c792725..ae5d999b 100644 --- a/test/test_pb.cpp +++ b/test/test_pb.cpp @@ -6,6 +6,8 @@ #include "iguana/pb_reader.hpp" #include "iguana/pb_writer.hpp" +#if defined(__clang__) || defined(_MSC_VER) || \ + (defined(__GNUC__) && __GNUC__ > 8) void print_hex_str(const std::string &str) { std::ostringstream oss; oss << std::hex << std::setfill('0'); @@ -15,10 +17,9 @@ void print_hex_str(const std::string &str) { std::cout << oss.str() << std::endl; } -#define BASE(T) -#define PUBLIC(T) : public iguana::pb_base_impl +#define PUBLIC(T) : public iguana::base_impl -struct point_t BASE(point_t) { +struct point_t { point_t() = default; point_t(int a, double b) : x(a), y(b) {} int x; @@ -27,7 +28,7 @@ struct point_t BASE(point_t) { REFLECTION(point_t, x, y); namespace my_space { -struct inner_struct BASE(inner_struct) { +struct inner_struct { inner_struct() = default; inner_struct(int a, int b, int c) : x(a), y(b), z(c) {} int x; @@ -42,7 +43,7 @@ constexpr inline auto get_members_impl(inner_struct *) { } } // namespace my_space -struct test_pb_st1 BASE(test_pb_st1) { +struct test_pb_st1 { test_pb_st1() = default; test_pb_st1(int a, iguana::sint32_t b, iguana::sint64_t c) : x(a), y(b), z(c) {} @@ -59,7 +60,7 @@ struct test_pb_sts PUBLIC(test_pb_sts) { }; REFLECTION(test_pb_sts, list); -struct test_pb_st2 BASE(test_pb_st2) { +struct test_pb_st2 { test_pb_st2() = default; test_pb_st2(int a, iguana::fixed32_t b, iguana::fixed64_t c) : x(a), y(b), z(c) {} @@ -69,7 +70,7 @@ struct test_pb_st2 BASE(test_pb_st2) { }; REFLECTION(test_pb_st2, x, y, z); -struct test_pb_st3 BASE(test_pb_st3) { +struct test_pb_st3 { test_pb_st3() = default; test_pb_st3(int a, iguana::sfixed32_t b, iguana::sfixed64_t c) : x(a), y(b), z(c) {} @@ -79,7 +80,7 @@ struct test_pb_st3 BASE(test_pb_st3) { }; REFLECTION(test_pb_st3, x, y, z); -struct test_pb_st4 BASE(test_pb_st3) { +struct test_pb_st4 { test_pb_st4() = default; test_pb_st4(int a, std::string b) : x(a), y(std::move(b)) {} int x; @@ -87,7 +88,7 @@ struct test_pb_st4 BASE(test_pb_st3) { }; REFLECTION(test_pb_st4, x, y); -struct test_pb_st5 BASE(test_pb_st5) { +struct test_pb_st5 { test_pb_st5() = default; test_pb_st5(int a, std::string_view b) : x(a), y(b) {} int x; @@ -95,7 +96,7 @@ struct test_pb_st5 BASE(test_pb_st5) { }; REFLECTION(test_pb_st5, x, y); -struct test_pb_st6 BASE(test_pb_st6) { +struct test_pb_st6 { test_pb_st6() = default; test_pb_st6(std::optional a, std::optional b) : x(std::move(a)), y(std::move(b)) {} @@ -104,7 +105,7 @@ struct test_pb_st6 BASE(test_pb_st6) { }; REFLECTION(test_pb_st6, x, y); -struct pair_t BASE(pair_t) { +struct pair_t PUBLIC(pair_t) { pair_t() = default; pair_t(int a, int b) : x(a), y(b) {} int x; @@ -120,7 +121,7 @@ struct message_t PUBLIC(message_t) { }; REFLECTION(message_t, id, t); -struct test_pb_st8 BASE(test_pb_st8) { +struct test_pb_st8 { test_pb_st8() = default; test_pb_st8(int a, pair_t b, message_t c) : x(a), y(b), z(c) {} @@ -130,7 +131,7 @@ struct test_pb_st8 BASE(test_pb_st8) { }; REFLECTION(test_pb_st8, x, y, z); -struct test_pb_st9 BASE(test_pb_st9) { +struct test_pb_st9 { test_pb_st9() = default; test_pb_st9(int a, std::vector b, std::string c) : x(a), y(std::move(b)), z(std::move(c)) {} @@ -140,7 +141,7 @@ struct test_pb_st9 BASE(test_pb_st9) { }; REFLECTION(test_pb_st9, x, y, z); -struct test_pb_st10 BASE(test_pb_st10) { +struct test_pb_st10 { test_pb_st10() = default; test_pb_st10(int a, std::vector b, std::string c) : x(a), y(std::move(b)), z(std::move(c)) {} @@ -161,7 +162,7 @@ struct test_pb_st11 PUBLIC(test_pb_st11) { }; REFLECTION(test_pb_st11, x, y, z); -struct test_pb_st12 BASE(test_pb_st12) { +struct test_pb_st12 { test_pb_st12() = default; test_pb_st12(int a, std::map b, std::map c) @@ -173,7 +174,7 @@ struct test_pb_st12 BASE(test_pb_st12) { }; REFLECTION(test_pb_st12, x, y, z); -struct test_pb_st13 BASE(test_pb_st13) { +struct test_pb_st13 { test_pb_st13() = default; test_pb_st13(int a, std::map b, std::string c) : x(a), y(std::move(b)), z(std::move(c)) {} @@ -188,7 +189,7 @@ enum class colors_t { red, black }; enum level_t { debug, info }; -struct test_pb_st14 BASE(test_pb_st14) { +struct test_pb_st14 { test_pb_st14() = default; test_pb_st14(int a, colors_t b, level_t c) : x(a), y(b), z(c) {} int x; @@ -198,7 +199,7 @@ struct test_pb_st14 BASE(test_pb_st14) { REFLECTION(test_pb_st14, x, y, z); namespace client { -struct person BASE(person) { +struct person { person() = default; person(std::string s, int d) : name(s), age(d) {} std::string name; @@ -208,16 +209,14 @@ struct person BASE(person) { REFLECTION(person, name, age); } // namespace client -struct my_struct BASE(my_struct) { - my_struct() = default; - my_struct(int a, bool b, iguana::fixed64_t c) : x(a), y(b), z(c) {} +struct my_struct { int x; bool y; iguana::fixed64_t z; }; REFLECTION(my_struct, x, y, z); -struct nest1 BASE(nest1) { +struct nest1 PUBLIC(nest1) { nest1() = default; nest1(std::string s, my_struct t, int d) : name(std::move(s)), value(t), var(d) {} @@ -225,10 +224,9 @@ struct nest1 BASE(nest1) { my_struct value; int var; }; - REFLECTION(nest1, name, value, var); -struct numer_st BASE(numer_st) { +struct numer_st PUBLIC(numer_st) { numer_st() = default; numer_st(bool x, double y, float z) : a(x), b(y), c(z) {} bool a; @@ -237,6 +235,78 @@ struct numer_st BASE(numer_st) { }; REFLECTION(numer_st, a, b, c); +TEST_CASE("test reflection") { + { + auto t = iguana::create_instance("nest1"); + std::vector fields_name = t->get_fields_name(); + CHECK(fields_name == std::vector{"name", "value", "var"}); + + my_struct mt{2, true, {42}}; + t->set_field_value("value", mt); + t->set_field_value("name", std::string("test")); + t->set_field_value("var", 41); + nest1 *st = dynamic_cast(t.get()); + auto p = *st; + std::cout << p.name << "\n"; + auto &r0 = t->get_field_value("name"); + CHECK(r0 == "test"); + auto &r = t->get_field_value("var"); + CHECK(r == 41); + auto &r1 = t->get_field_value("value"); + CHECK(r1.x == 2); + + t->set_field_value("name", "hello"); + auto &s = t->get_field_value("name"); + CHECK(s == "hello"); + + CHECK_THROWS_AS(t->set_field_value("name", 42), std::invalid_argument); + } + { + auto t = iguana::create_instance("pair_t"); + t->set_field_value("x", 12); + t->set_field_value("y", 24); + auto &r0 = t->get_field_value("x"); + CHECK(r0 == 12); + auto &r = t->get_field_value("y"); + CHECK(r == 24); + + std::string str; + t->to_pb(str); + + pair_t t1; + t1.from_pb(str); + + pair_t *st = dynamic_cast(t.get()); + CHECK(st->x == t1.x); + CHECK(st->y == t1.y); + } + auto t = iguana::create_instance("numer_st"); + t->set_field_value("a", true); + t->set_field_value("b", 25); + t->set_field_value("c", 42); + auto &r0 = t->get_field_value("a"); + CHECK(r0); + auto &r = t->get_field_value("b"); + CHECK(r == 25); + auto &r1 = t->get_field_value("c"); + CHECK(r1 == 42); + + numer_st *st = dynamic_cast(t.get()); + CHECK(st->a == true); + CHECK(st->b == 25); + CHECK(st->c == 42); + + std::string str; + t->to_pb(str); + + numer_st t1; + t1.from_pb(str); + + CHECK(st->a == t1.a); + CHECK(st->b == t1.b); + CHECK(st->c == t1.c); +} + TEST_CASE("test struct_pb") { { my_space::inner_struct inner{41, 42, 43}; @@ -377,7 +447,7 @@ TEST_CASE("test struct_pb") { std::string s; m->to_pb(s); - std::shared_ptr base = m; + std::shared_ptr base = m; std::string str; base->to_pb(str); @@ -510,7 +580,7 @@ TEST_CASE("test members") { val); } -struct test_variant BASE(test_variant) { +struct test_variant { test_variant() = default; test_variant(int a, std::variant b, double c) : x(a), y(std::move(b)), z(c) {} @@ -569,6 +639,7 @@ TEST_CASE("test variant") { CHECK(std::get(st2.y) == 3.88); } } +#endif DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) int main(int argc, char **argv) { return doctest::Context(argc, argv).run(); }