C++利用宏實現(xiàn)類成員反射詳解
序
本文我們看下用宏來實現(xiàn)反射,在一些伙伴使用c++版本還不是那么高的情況下但又需要反射的一些技巧,這里使用的代碼是iguana里的實現(xiàn),我對它關(guān)于反射的地方提煉一下,稍微改動了下。iguana是比較優(yōu)秀的序列化庫,其中使用反射為基礎(chǔ),性能很好?,F(xiàn)在在yalantinglibs中也可以找到。
當(dāng)然使用的時候可以直接使用iguana,我這里解釋下其中的相關(guān)原理。
如何使用
以如下Person這個結(jié)構(gòu)體為例
struct Person{
int a;
float b;
};
REFLECTION(Person, a, b)
這里結(jié)構(gòu)體就是普通的結(jié)構(gòu)體,不過需要用戶做的是,需要定義REFLECTION宏,其中第一個參數(shù)是類(結(jié)構(gòu)體)名,然后是各個成員名。
然后其實就可以使用反射了:
using Members = decltype(iguana_reflect_members(std::declval<Person>()));
std::cout << Members::value() << std::endl; // count
auto membersPtr = Members::apply_impl(); // ptr(tuple)
Person p{};
p.*std::get<0>(membersPtr) = 34;
p.*std::get<1>(membersPtr) = 4.1f;
std::cout << p.a << std::endl; // 34
std::cout << p.b << std::endl; // 4.1
REFLECTION中會生成一個iguana_reflect_members函數(shù),該函數(shù)簡單展示下:
auto iguana_reflect_members(STRUCT_NAME const &) {
struct reflect_members {
// ...(略)
};
return reflect_members{};
}
可以看到iguana_reflect_members函數(shù)內(nèi)部定義了一個用來提供成員反射信息的b結(jié)構(gòu)體,然后構(gòu)造并返回。
繼續(xù)返回到使用示例那里,第一句通過decltype和declval搭檔拿到了iguana_reflect_members返回值類型。第二句我們先打印出來Person這個結(jié)構(gòu)體的成員個數(shù)。然后再使用Members::apply_impl函數(shù)獲取到Person的成員指針。這里使用成員指針就可以對其成員進行訪問了。返回值的類型是tuple,我們使用std::get來對tuple進行遍歷訪問。
如何實現(xiàn)
那么如何實現(xiàn)獲取到成員的個數(shù),及存儲成員指針這些呢,我們?nèi)ソ议_REFLECTION的真面目;
#define REFLECTION(STRUCT_NAME, ...) \ MAKE_META_DATA_IMPL(STRUCT_NAME, GET_ARG_COUNT(__VA_ARGS__), __VA_ARGS__)
看上去很簡單,調(diào)用MAKE_META_DATA_IMPL的宏,MAKE_META_DATA_IMPL需要STRUCT_NAME,count以及所有的剩余參數(shù),也就是類的各個成員??梢钥吹?code>GET_ARG_COUNT可以獲取到成員的個數(shù)。
成員個數(shù)
那我們先去看下GET_ARG_COUNT的實現(xiàn):
#define MARCO_EXPAND(...) __VA_ARGS__ #define GET_ARG_COUNT_INNER(...) MARCO_EXPAND(ARG_N(__VA_ARGS__)) #define GET_ARG_COUNT(...) GET_ARG_COUNT_INNER(__VA_ARGS__, RSEQ_N())
GET_ARG_COUNT調(diào)用GET_ARG_COUNT_INNER,將成員和RSEQ_N拼接起來,傳遞給ARG_N這個宏作為參數(shù)調(diào)用,那就要看下ARG_N和RSEQ_N的聲明:
#define ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, \
_9, _10, _11, _12, _13, _14, _15, \
_16, _17, _18, _19, _20, _21, _22, \
_23, _24, _25, _26, _27, _28, _29, \
_30, _31, _32, N, ...) \
N
#define RSEQ_N() \
32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, \
19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, \
5, 4, 3, 2, 1, 0
ARG_N可以看到接收32個參數(shù)及N,然后就能表示N。RSEQ_N()僅僅就是32~0的數(shù)字序列,那么將成員(假設(shè)3個成員)和RSEQ_N()組合起來就是類似這樣
member1, member2, member3, 32, 31, 30, ... 0
然后傳遞給ARG_N時,member1對應(yīng)_1,member2對應(yīng)_2,member3對應(yīng)_3,32對應(yīng)_4,... 這樣計算參數(shù)個數(shù)就是3(3個成員)+ 33(32~0),那么進一步這里N就是第33個元素,再進一步可以這樣理解:如果沒有成員,僅僅RSEQ_N()傳遞進來時,一一匹配到0正好對應(yīng)N,那么當(dāng)前邊加了3個成員,那么就是將RSEQ_N()后移3個元素,那就是N正好對應(yīng)3,也就是成員的個數(shù)。
成員指針
再次回到我們實現(xiàn)的最開始部分:
#define REFLECTION(STRUCT_NAME, ...) \ MAKE_META_DATA_IMPL(STRUCT_NAME, GET_ARG_COUNT(__VA_ARGS__), __VA_ARGS__)
GET_ARG_COUNT這個我們已經(jīng)明白了,那么我們繼續(xù)去看下MAKE_META_DATA_IMPL宏做了什么:
#define MAKE_META_DATA_IMPL(STRUCT_NAME, N, ...) \
[[maybe_unused]] inline static auto \
iguana_reflect_members(STRUCT_NAME const &) { \
struct reflect_members { \
constexpr decltype(auto) static apply_impl(){ \
return std::make_tuple( \
MAKE_ARG_LIST(N, &STRUCT_NAME::FIELD, \
__VA_ARGS__)); \
} \
using size_type = \
std::integral_constant<size_t, N>; \
constexpr static size_t value() { \
return size_type::value; \
} \
}; \
return reflect_members{}; \
}
MAKE_META_DATA_IMPL這個宏就是定義了iguana_reflect_members(STRUCT_NAME const &)這樣一個函數(shù),大致的結(jié)構(gòu)我們前邊也說過,值得注意的有兩個點:
- 因為參數(shù)會根據(jù)傳入的類名各不一樣,所以不用擔(dān)心函數(shù)簽名重復(fù)的問題;
- 定義了函數(shù)內(nèi)部結(jié)構(gòu)體,返回了內(nèi)部結(jié)構(gòu)體對象,但是如我們最開始使用那樣,僅僅通過decval拿到這個內(nèi)部結(jié)構(gòu)體的類型,而不會真正調(diào)用
iguana_reflect_members函數(shù)。
繼續(xù)看reflect_members結(jié)構(gòu)體,從簡單的看起,value函數(shù)就是返回剛剛傳進來的N,這里size_type就是一個值為N的結(jié)構(gòu)體,正好也是返回size_type::value, 所以就是N。
apply_impl函數(shù)里邊稍微有點復(fù)雜,因為成員的指針類型各不一樣,所以使用tuple來存放,內(nèi)部就是對MAKE_ARG_LIST的調(diào)用,那我們也再跳轉(zhuǎn)到實現(xiàn)瞧瞧:
#define MACRO_CONCAT(A, B) MACRO_CONCAT1(A, B) #define MACRO_CONCAT1(A, B) A##_##B #define MAKE_ARG_LIST(N, op, arg, ...) \ MACRO_CONCAT(MAKE_ARG_LIST, N)(op, arg, __VA_ARGS__)
由于宏的一些特性,我們不得不使用MACRO_CONCAT1及MACRO_CONCAT對宏與一些字符進行拼接。這里是把MAKE_ARG_LIST和下劃線以及N進行拼接,那么MAKE_ARG_LIST實際上調(diào)用的是MAKE_ARG_LIST_N,但是這里的N是實際的成員個數(shù),還是假定是3個成員,那么調(diào)用就是這樣的MAKE_ARG_LIST_3(op, arg, __VA_ARGS__),同時這里還用arg來拆出來第一個元素,類似于我們解參數(shù)包方式。
那么我們還需要再次去看MAKE_ARG_LIST_3的實現(xiàn):
#define MAKE_ARG_LIST_1(op, arg, ...) op(arg) #define MAKE_ARG_LIST_2(op, arg, ...) \ op(arg), MARCO_EXPAND(MAKE_ARG_LIST_1(op, __VA_ARGS__)) #define MAKE_ARG_LIST_3(op, arg, ...) \ op(arg), MARCO_EXPAND(MAKE_ARG_LIST_2(op, __VA_ARGS__)) #define MAKE_ARG_LIST_4(op, arg, ...) \ op(arg), MARCO_EXPAND(MAKE_ARG_LIST_3(op, __VA_ARGS__)) //...(略) #define MAKE_ARG_LIST_32(op, arg, ...) \ op(arg), MARCO_EXPAND(MAKE_ARG_LIST_31(op, __VA_ARGS__))
因為MAKE_ARG_LIST_N和成員個數(shù)有關(guān),這里也還是定義了32個宏,中間我們省略了很多,實現(xiàn)很簡單,先看MAKE_ARG_LIST_1,就是對op的調(diào)用,再看MAKE_ARG_LIST_2首先對第一個參數(shù)進行op調(diào)用,剩下的參數(shù)去調(diào)用MAKE_ARG_LIST_1,然后使用逗號拼接。以此類推,如果是32的話,就是對32個參數(shù)分別op調(diào)用。
我們繼續(xù)跳回到成員指針獲取的那里:
#define FIELD(t) t
struct reflect_members {
constexpr decltype(auto) static apply_impl() {
return std::make_tuple(
MAKE_ARG_LIST(N,
&STRUCT_NAME::FIELD,__VA_ARGS__)
);
}
};
我們明白了MAKE_ARG_LIST的含義,就是分別對各個參數(shù)進行op操作,這里op正好對應(yīng)于&STRUCT_NAME::FIELD, FIELD就是一個封裝了一個括號方便調(diào)用,那也就是&STRUCT_NAME::各個成員,這也就是成員的指針。
最終推導(dǎo)展示
有了上邊的講解,我們使用clion可以看到最開始Person的REFLECTION(Person, a, b)表示的是啥:

增加成員類型
盡管可以通過指針獲取到各個成員的類型,但是為了使用方便,我們在reflect_members中增加一個各個成員的類型,我們使用類型列表來存放:
template<typename... Types>
struct TypeList {};
這樣reflect_members中成員類型列表可能實現(xiàn)就是這樣:
struct reflect_members {
using member_types = TypeList<
MAKE_ARG_LIST(N, decltype, MAKE_ARG_LIST(N,
STRUCT_NAME::FIELD, __VA_ARGS__))
>;
};
可以看到,TypeList中使用兩個MAKE_ARG_LIST嵌套實現(xiàn),首先對每個成員參數(shù)STRUCT_NAME::FIELD操作,然后在對操作后的成員參數(shù)進行decltype操作,以上面Person為例可以看到最終member_types是這樣的:
using member_types = TypeList<decltype(Person::a), decltype(Person::b)>;
然后我們?nèi)绾问褂肨ypeList,需要配套一些操作方法,我這里目前只實現(xiàn)了根據(jù)順序來獲取成員的類型,類似這樣:
using MemberTypes = Members::member_types; TypeByIndex<0, MemberTypes>::type a1 = 12; // int TypeByIndex<1, MemberTypes>::type b1 = 12.87; // float
我們也簡單看下TypeByIndex如何實現(xiàn):
template<int Index, typename TL>
struct TypeByIndex {
using type = typename TypeByIndex<Index - 1,
typename ListPop<TL>::type>::type;
};
template<typename TypeList>
struct TypeByIndex<0, TypeList> {
using type = typename ListHead<TypeList>::type;
};
TypeByIndex模板元函數(shù)實現(xiàn)如上,針對于Index為0進行特化,那就是說只需要獲取TypeList中第一個類型即可,也就是這里的ListHead。否則就走主模板,主模板是一個遞歸的,將Index減1,TypeList給pop出第一個元素,也即ListPop操作,繼續(xù)調(diào)用TypeByIndex,直到Index為0,正好對應(yīng)于相對應(yīng)的類型。
也看下ListHead和ListPop的實現(xiàn):
template<typename TL>
struct ListHead;
template<typename Head, typename... Args>
struct ListHead<TypeList<Head, Args...>> {
using type = Head;
};
template<typename Tp>
struct ListPop {
using type = TypeList<>;
};
template<typename Head, typename... Args>
struct ListPop<TypeList<Head, Args...>> {
using type = TypeList<Args...>;
};
- 先看
ListHead,主模板僅僅是一個聲明,特化版本特化出來TypeList<Head, Args...>直接獲取到Head。 - 再來看
ListPop,主模板認為是一個空的TypeList,特化模板則是特化出來TypeList<Head, Args...>形式,那樣正好把第一個元素和后邊元素分開,進一步拿到后邊類型重新組裝成新的TypeList。
總結(jié)
這里我們使用宏來實現(xiàn)了結(jié)構(gòu)體(或類)成員的反射,包括成員的個數(shù),成員的指針,成員的類型。有了這些我們就可以做一些基本的操作了,比如說一些序列化結(jié)構(gòu)體等等。
我們還展示了TypeList及相關(guān)的簡單操作。當(dāng)然你如果需要的話,也可以將TypeList操作豐富起來。
以上就是C++利用宏實現(xiàn)類成員反射詳解的詳細內(nèi)容,更多關(guān)于C++反射的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
深入講解C++數(shù)據(jù)類型轉(zhuǎn)換的相關(guān)函數(shù)的知識
這篇文章主要介紹了深入講解C++數(shù)據(jù)類型轉(zhuǎn)換的相關(guān)函數(shù)的知識,包括類型轉(zhuǎn)換運算符函數(shù)等內(nèi)容,需要的朋友可以參考下2015-09-09
Qt中正則表達式的常見用法(QRegularExpression類)
正則表達式即一個文本匹配字符串的一種模式,Qt中使用QRegExp類進行模式匹配,下面這篇文章主要給大家介紹了關(guān)于Qt中正則表達式的常見用法,文中介紹的是QRegularExpression類的相關(guān)資料,需要的朋友可以參考下2024-05-05
使用C++的string實現(xiàn)高精度加法運算的實例代碼
下面小編就為大家?guī)硪黄褂肅++的string實現(xiàn)高精度加法運算的實例代碼。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-09-09
C++實現(xiàn)學(xué)生考勤信息管理系統(tǒng)
這篇文章主要為大家詳細介紹了C++實現(xiàn)學(xué)生考勤信息管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-12-12

