C++ 利用宏批量定義類型/常量信息

標題起得比較模糊,是因爲我才疏學淺,不知道怎麼去描述這個事情

探究的興趣來源於 V8 的代碼:

bytecode.h

它定義了一段我看不懂的宏
[code-0]

#define BYTECODE_LIST(V)                                                       \
  /* Extended width operands */                                                \
  V(Wide, AccumulatorUse::kNone)                                               \
  V(ExtraWide, AccumulatorUse::kNone)                                          \
                                                                               \
  /* Debug Breakpoints - one for each possible size of unscaled bytecodes */   \
  /* and one for each operand widening prefix bytecode                    */   \
  V(DebugBreakWide, AccumulatorUse::kReadWrite)                                \
  V(DebugBreakExtraWide, AccumulatorUse::kReadWrite)                           \
  V(DebugBreak0, AccumulatorUse::kReadWrite)                                   \
  V(DebugBreak1, AccumulatorUse::kReadWrite, OperandType::kReg)                \
  V(DebugBreak2, AccumulatorUse::kReadWrite, OperandType::kReg,                \
    OperandType::kReg)                                                         \
  V(DebugBreak3, AccumulatorUse::kReadWrite, OperandType::kReg,                
//假設它只有這麼長

後來我看到這個宏定義的作用是:
對於每一行 V(...),用裏面 ... 的內容作爲 V 的參數,生成一個表達式。例如:

#define FCK(...) fck(...);
BYTECODE_LIST(FCK)

那麼我們相當於得到了這樣一組調用:

fck(Wide, AccumulatorUse::kNone);
fck(ExtraWide, AccumulatorUse::kNone);
fck(DebugBreakWide, AccumulatorUse::kReadWrite); 

這裏我們 FCK 宏的定義可以更騷一點,它可以不是函數調用,也可以是別的,比如定義一個類,各位玩的比我好。
有了這個用例,我們就可以做更巧妙的事情。我們可以把一組類型信息(或者常量信息,等等)填入到 [code-0] 這樣的‘宏列表’(這個名字是我自己起的,下面還會繼續用到)中,讓它成爲編譯器可以讀取的信息,從而讓我們把這些長串的信息進行自動化的管理,並且不耗費運行時間。當然,我不知道 C/C++ 有沒有更加直白的方法在編譯器可以做到這一點,接下來是我自己的一個例子,就是用宏列表批量產生類的定義

下面是一個我模仿 bytecode.h 寫的一段“宏列表”,它的內容不重要,重要在於格式(和規模),需要注意的是
OperandType::Symbol*, OperandType::Index* 是類型。

namespace IR
{
#define BYTECODE_LIST(C)\
    /* visit attribute arg1 of arg0 and load it to accumulator */\
    C(LoadAttributeToAcc, AccumulatorUse::Write, OperandType::Symbol*, OperandType::Index*)\
    /* call function arg0 with pushed parameters, with return value overwriting Acc */\
    C(CallFunc, AccumulatorUse::Write, OperandType::Index*)\
    /* push function parameter */\
    C(PushParamImm64, AccumulatorUse::None, OperandType::Imm64*)\
    C(PushParamSymbol, AccumulatorUse::None, OperandType::Symbol*)\
    C(PushParamAcc, AccumulatorUse::Read)\
    /* jump to arg0 if Acc is true */\
    C(JumpOnTrue, AccumulatorUse::Read, OperandType::Label*)\
    /* jump to arg1 if Acc is false */\
    C(JumpOnFalse, AccumulatorUse::Write, OperandType::Label*)\
    /* logical or with Acc */\
    C(Or, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
    C(OrImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
    /* logical and with Acc */\
    C(And, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
    C(AndImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
    /* logical equal with Acc */\
    C(Equal, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
    C(EqualImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
    /* logical not-equal with Acc */\
    C(NotEqual, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
    C(NotEqualImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
    /* signed GE with Acc */\
    C(GreaterEqual, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
    C(GreaterEqualImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
    /* signed LE with Acc */\
    C(LessEqual, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
    C(LessEqualImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
    /* signed GT with Acc */\
    C(GreaterThan, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
    C(GreaterThanImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
    /* signed LT with Acc */\
    C(LessThan, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
    C(LessThanImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
    /* add with Acc */\
    C(Add, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
    C(AddImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
    /* subtract with Acc */\
    C(Sub, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
    C(SubImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
    /* multiply with Acc */\
    C(Mul, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
    C(MulImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
    /* divide with Acc */\
    C(Div, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
    C(DivImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
    /* mod with Acc */\
    C(Mod, AccumulatorUse::ReadWrite, OperandType::Symbol*)\
    C(ModImm64, AccumulatorUse::ReadWrite, OperandType::Imm64*)\
    /* incremenmt Acc */\
    C(Inc, AccumulatorUse::ReadWrite)\
    /* decrement Acc */\
    C(Decre, AccumulatorUse::ReadWrite)\
    /* negate Acc, that is to multiply with -1 */\
    C(Neg, AccumulatorUse::ReadWrite)\

}

接下來我用這樣的宏就可以批量生成名字爲 C{Name} (比如CInc, CDecre) 這樣的類,並且利用了對應的類型信息:

namespace IR
{
    namespace BytecodeClass
    {
#define CLASS_DEF(name, acc_use, ...)\
    class C##name {\
    public:\
        void init(__VA_ARGS__);\
        void generate_code();\
        const char * get_name() {\
            return #name;\
        }\
        IR::AccumulatorUse::Type get_acc_use() {\
            return acc_use;\
        }\
    };

    BYTECODE_LIST(CLASS_DEF)
#undef CLASS_DEF
    }
}

但是這樣顯然有一個問題:我們在實現的時候,沒有辦法一次把所有類的 init , generate_code 都實現了,怎麼辦?(如果不實現,編譯時就會有未定義的錯誤)

先在宏裏面批量定義一個定義,然後再重新定義?C++的編譯器不支持重定義這樣的操作,哪怕是編譯期可以確定。利用函數指針是一條路,但是有運行時開銷,這和我們初衷不符,但也不是不行。

後來我發現,我只能發現一條路:將需要定義的方法定義爲虛方法,然後實現的時候逐一繼承這些虛類,這樣既可以利用過程信息,又可以利用我們定義好的宏的內存信息。於是我們把這個宏改一下,需要暫時不定義的地方改成 virtual,並類名也做了修改:

namespace IR
{
    namespace BytecodeClass
    {
#define CLASS_DEF(name, acc_use, ...)\
    class Virtual##name {\
    public:\
        virtual void init(__VA_ARGS__) = 0;\
        virtual void generate_code() = 0;\
        const char * get_name() {\
            return #name;\
        }\
        IR::AccumulatorUse::Type get_acc_use() {\
            return acc_use;\
        }\
    };

    BYTECODE_LIST(CLASS_DEF)
#undef CLASS_DEF
    }
}

這樣就很爽了,我們實現的時候,就繼承一個虛類出來,然後還可以添加成員屬性,好不快活,並且還能得到自動的類型信息!

這裏寫圖片描述

智能提示裏的語法信息來自

C(LoadAttributeToAcc, AccumulatorUse::Write, OperandType::Symbol*, OperandType::Index*)\

從信息流動來回顧一下過程,並且驗證一下理解:
1.首先這段信息在“宏列表中被定義”’
2. CLASS_DEF 中,我們通過變長宏參數列表 (name, acc_use, ...)... 這段,也就是 OperandType::Symbol*, OperandType::Index* 定義了虛函數 init 的參數
3. LoadAttributeToAcc 繼承了宏定義產生的類 VirtualLoadAttributeToAcc 它得到了父類虛函數 init 的參數信息。

不得不說 V8 的工程師是玩的溜。C++ 的預處理器也很硬核。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章