欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++利用宏實(shí)現(xiàn)類成員反射詳解

 更新時(shí)間:2024年01月10日 11:36:56   作者:ticking  
這篇文章主要為大家詳細(xì)介紹了C++如何利用宏實(shí)現(xiàn)類成員反射,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,有興趣的小伙伴可以了解一下

本文我們看下用宏來實(shí)現(xiàn)反射,在一些伙伴使用c++版本還不是那么高的情況下但又需要反射的一些技巧,這里使用的代碼是iguana里的實(shí)現(xiàn),我對(duì)它關(guān)于反射的地方提煉一下,稍微改動(dòng)了下。iguana是比較優(yōu)秀的序列化庫,其中使用反射為基礎(chǔ),性能很好?,F(xiàn)在在yalantinglibs中也可以找到。

當(dāng)然使用的時(shí)候可以直接使用iguana,我這里解釋下其中的相關(guān)原理。

如何使用

以如下Person這個(gè)結(jié)構(gòu)體為例

struct Person{
    int a;
    float b;
};
REFLECTION(Person, a, b)

這里結(jié)構(gòu)體就是普通的結(jié)構(gòu)體,不過需要用戶做的是,需要定義REFLECTION宏,其中第一個(gè)參數(shù)是類(結(jié)構(gòu)體)名,然后是各個(gè)成員名。

然后其實(shí)就可以使用反射了:

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中會(huì)生成一個(gè)iguana_reflect_members函數(shù),該函數(shù)簡單展示下:

auto iguana_reflect_members(STRUCT_NAME const &) {                                                  
    struct reflect_members {                                                  
        // ...(略)
    };                               
    return reflect_members{}; 
}

可以看到iguana_reflect_members函數(shù)內(nèi)部定義了一個(gè)用來提供成員反射信息的b結(jié)構(gòu)體,然后構(gòu)造并返回。
繼續(xù)返回到使用示例那里,第一句通過decltypedeclval搭檔拿到了iguana_reflect_members返回值類型。第二句我們先打印出來Person這個(gè)結(jié)構(gòu)體的成員個(gè)數(shù)。然后再使用Members::apply_impl函數(shù)獲取到Person的成員指針。這里使用成員指針就可以對(duì)其成員進(jìn)行訪問了。返回值的類型是tuple,我們使用std::get來對(duì)tuple進(jìn)行遍歷訪問。

如何實(shí)現(xiàn)

那么如何實(shí)現(xiàn)獲取到成員的個(gè)數(shù),及存儲(chǔ)成員指針這些呢,我們?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ù),也就是類的各個(gè)成員。可以看到GET_ARG_COUNT可以獲取到成員的個(gè)數(shù)。

成員個(gè)數(shù)

那我們先去看下GET_ARG_COUNT的實(shí)現(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這個(gè)宏作為參數(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個(gè)參數(shù)及N,然后就能表示N。RSEQ_N()僅僅就是32~0的數(shù)字序列,那么將成員(假設(shè)3個(gè)成員)和RSEQ_N()組合起來就是類似這樣

member1, member2, member3, 32, 31, 30, ... 0

然后傳遞給ARG_N時(shí),member1對(duì)應(yīng)_1,member2對(duì)應(yīng)_2,member3對(duì)應(yīng)_3,32對(duì)應(yīng)_4,... 這樣計(jì)算參數(shù)個(gè)數(shù)就是3(3個(gè)成員)+ 33(32~0),那么進(jìn)一步這里N就是第33個(gè)元素,再進(jìn)一步可以這樣理解:如果沒有成員,僅僅RSEQ_N()傳遞進(jìn)來時(shí),一一匹配到0正好對(duì)應(yīng)N,那么當(dāng)前邊加了3個(gè)成員,那么就是將RSEQ_N()后移3個(gè)元素,那就是N正好對(duì)應(yīng)3,也就是成員的個(gè)數(shù)。

成員指針

再次回到我們實(shí)現(xiàn)的最開始部分:

#define REFLECTION(STRUCT_NAME, ...)                                    \
 MAKE_META_DATA_IMPL(STRUCT_NAME, GET_ARG_COUNT(__VA_ARGS__), __VA_ARGS__)

GET_ARG_COUNT這個(gè)我們已經(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這個(gè)宏就是定義了iguana_reflect_members(STRUCT_NAME const &)這樣一個(gè)函數(shù),大致的結(jié)構(gòu)我們前邊也說過,值得注意的有兩個(gè)點(diǎn):

  • 因?yàn)閰?shù)會(huì)根據(jù)傳入的類名各不一樣,所以不用擔(dān)心函數(shù)簽名重復(fù)的問題;
  • 定義了函數(shù)內(nèi)部結(jié)構(gòu)體,返回了內(nèi)部結(jié)構(gòu)體對(duì)象,但是如我們最開始使用那樣,僅僅通過decval拿到這個(gè)內(nèi)部結(jié)構(gòu)體的類型,而不會(huì)真正調(diào)用iguana_reflect_members函數(shù)。

繼續(xù)看reflect_members結(jié)構(gòu)體,從簡單的看起,value函數(shù)就是返回剛剛傳進(jìn)來的N,這里size_type就是一個(gè)值為N的結(jié)構(gòu)體,正好也是返回size_type::value, 所以就是N。

apply_impl函數(shù)里邊稍微有點(diǎn)復(fù)雜,因?yàn)槌蓡T的指針類型各不一樣,所以使用tuple來存放,內(nèi)部就是對(duì)MAKE_ARG_LIST的調(diào)用,那我們也再跳轉(zhuǎn)到實(shí)現(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_CONCAT1MACRO_CONCAT對(duì)宏與一些字符進(jìn)行拼接。這里是把MAKE_ARG_LIST和下劃線以及N進(jìn)行拼接,那么MAKE_ARG_LIST實(shí)際上調(diào)用的是MAKE_ARG_LIST_N,但是這里的N是實(shí)際的成員個(gè)數(shù),還是假定是3個(gè)成員,那么調(diào)用就是這樣的MAKE_ARG_LIST_3(op, arg, __VA_ARGS__),同時(shí)這里還用arg來拆出來第一個(gè)元素,類似于我們解參數(shù)包方式。

那么我們還需要再次去看MAKE_ARG_LIST_3的實(shí)現(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__))

因?yàn)?code>MAKE_ARG_LIST_N和成員個(gè)數(shù)有關(guān),這里也還是定義了32個(gè)宏,中間我們省略了很多,實(shí)現(xiàn)很簡單,先看MAKE_ARG_LIST_1,就是對(duì)op的調(diào)用,再看MAKE_ARG_LIST_2首先對(duì)第一個(gè)參數(shù)進(jìn)行op調(diào)用,剩下的參數(shù)去調(diào)用MAKE_ARG_LIST_1,然后使用逗號(hào)拼接。以此類推,如果是32的話,就是對(duì)32個(gè)參數(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的含義,就是分別對(duì)各個(gè)參數(shù)進(jìn)行op操作,這里op正好對(duì)應(yīng)于&STRUCT_NAME::FIELD, FIELD就是一個(gè)封裝了一個(gè)括號(hào)方便調(diào)用,那也就是&STRUCT_NAME::各個(gè)成員,這也就是成員的指針。

最終推導(dǎo)展示

有了上邊的講解,我們使用clion可以看到最開始PersonREFLECTION(Person, a, b)表示的是啥:

增加成員類型

盡管可以通過指針獲取到各個(gè)成員的類型,但是為了使用方便,我們?cè)?code>reflect_members中增加一個(gè)各個(gè)成員的類型,我們使用類型列表來存放:

template<typename... Types>
struct TypeList {};

這樣reflect_members中成員類型列表可能實(shí)現(xiàn)就是這樣:

struct reflect_members {
  using member_types = TypeList<
    MAKE_ARG_LIST(N, decltype, MAKE_ARG_LIST(N,
        STRUCT_NAME::FIELD, __VA_ARGS__))
    >;
};

可以看到,TypeList中使用兩個(gè)MAKE_ARG_LIST嵌套實(shí)現(xiàn),首先對(duì)每個(gè)成員參數(shù)STRUCT_NAME::FIELD操作,然后在對(duì)操作后的成員參數(shù)進(jìn)行decltype操作,以上面Person為例可以看到最終member_types是這樣的:

using member_types = TypeList<decltype(Person::a), decltype(Person::b)>;

然后我們?nèi)绾问褂肨ypeList,需要配套一些操作方法,我這里目前只實(shí)現(xiàn)了根據(jù)順序來獲取成員的類型,類似這樣:

using MemberTypes = Members::member_types;
   
TypeByIndex<0, MemberTypes>::type a1 = 12;    // int
TypeByIndex<1, MemberTypes>::type b1 = 12.87; // float

我們也簡單看下TypeByIndex如何實(shí)現(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ù)實(shí)現(xiàn)如上,針對(duì)于Index為0進(jìn)行特化,那就是說只需要獲取TypeList中第一個(gè)類型即可,也就是這里的ListHead。否則就走主模板,主模板是一個(gè)遞歸的,將Index減1,TypeList給pop出第一個(gè)元素,也即ListPop操作,繼續(xù)調(diào)用TypeByIndex,直到Index為0,正好對(duì)應(yīng)于相對(duì)應(yīng)的類型。

也看下ListHeadListPop的實(shí)現(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,主模板僅僅是一個(gè)聲明,特化版本特化出來TypeList<Head, Args...>直接獲取到Head。
  • 再來看ListPop,主模板認(rèn)為是一個(gè)空的TypeList,特化模板則是特化出來TypeList<Head, Args...>形式,那樣正好把第一個(gè)元素和后邊元素分開,進(jìn)一步拿到后邊類型重新組裝成新的TypeList。

總結(jié)

這里我們使用宏來實(shí)現(xiàn)了結(jié)構(gòu)體(或類)成員的反射,包括成員的個(gè)數(shù),成員的指針,成員的類型。有了這些我們就可以做一些基本的操作了,比如說一些序列化結(jié)構(gòu)體等等。

我們還展示了TypeList及相關(guān)的簡單操作。當(dāng)然你如果需要的話,也可以將TypeList操作豐富起來。

以上就是C++利用宏實(shí)現(xiàn)類成員反射詳解的詳細(xì)內(nèi)容,更多關(guān)于C++反射的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • VC中刪除類的兩種操作方法

    VC中刪除類的兩種操作方法

    這篇文章主要介紹了VC中刪除類的兩種操作方法,較為詳細(xì)的描述了在VC中實(shí)現(xiàn)刪除類的具體步驟,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2015-05-05
  • C++實(shí)現(xiàn)堆排序?qū)嵗榻B

    C++實(shí)現(xiàn)堆排序?qū)嵗榻B

    大家好,本篇文章主要講的是C++實(shí)現(xiàn)堆排序?qū)嵗榻B,感興趣的同學(xué)趕快來看一看吧,對(duì)你有幫助的話記得收藏一下,方便下次瀏覽
    2021-12-12
  • C++多態(tài)的示例詳解

    C++多態(tài)的示例詳解

    多態(tài)按字面的意思就是多種形態(tài)。當(dāng)類之間存在層次結(jié)構(gòu),并且類之間是通過繼承關(guān)聯(lián)時(shí),就會(huì)用到多態(tài)。本文將通過三個(gè)小案例讓大家更深入的了解一下C++的多態(tài),感興趣的可以了解一下
    2022-06-06
  • C++編程中逗號(hào)運(yùn)算符和條件運(yùn)算符的使用方法講解

    C++編程中逗號(hào)運(yùn)算符和條件運(yùn)算符的使用方法講解

    這篇文章主要介紹了C++編程中逗號(hào)運(yùn)算符和條件運(yùn)算符的使用方法講解,需要的朋友可以參考下
    2016-01-01
  • 深入講解C++數(shù)據(jù)類型轉(zhuǎn)換的相關(guān)函數(shù)的知識(shí)

    深入講解C++數(shù)據(jù)類型轉(zhuǎn)換的相關(guān)函數(shù)的知識(shí)

    這篇文章主要介紹了深入講解C++數(shù)據(jù)類型轉(zhuǎn)換的相關(guān)函數(shù)的知識(shí),包括類型轉(zhuǎn)換運(yùn)算符函數(shù)等內(nèi)容,需要的朋友可以參考下
    2015-09-09
  • 淺談C++11新引入的lambda表達(dá)式

    淺談C++11新引入的lambda表達(dá)式

    Lambda表達(dá)式(又稱Lambda函數(shù),英文原文是Lambda Expression),是C++11的新特性中非常實(shí)用的一個(gè)。
    2017-07-07
  • Qt中正則表達(dá)式的常見用法(QRegularExpression類)

    Qt中正則表達(dá)式的常見用法(QRegularExpression類)

    正則表達(dá)式即一個(gè)文本匹配字符串的一種模式,Qt中使用QRegExp類進(jìn)行模式匹配,下面這篇文章主要給大家介紹了關(guān)于Qt中正則表達(dá)式的常見用法,文中介紹的是QRegularExpression類的相關(guān)資料,需要的朋友可以參考下
    2024-05-05
  • C++中String類型的逆序方式

    C++中String類型的逆序方式

    這篇文章主要介紹了C++中String類型的逆序方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • 使用C++的string實(shí)現(xiàn)高精度加法運(yùn)算的實(shí)例代碼

    使用C++的string實(shí)現(xiàn)高精度加法運(yùn)算的實(shí)例代碼

    下面小編就為大家?guī)硪黄褂肅++的string實(shí)現(xiàn)高精度加法運(yùn)算的實(shí)例代碼。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-09-09
  • C++實(shí)現(xiàn)學(xué)生考勤信息管理系統(tǒng)

    C++實(shí)現(xiàn)學(xué)生考勤信息管理系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)學(xué)生考勤信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-12-12

最新評(píng)論