Devirtualization (去虛擬化)
Devirtualization 是 C++ 編譯器的一種最佳化,舉個例子:
struct foo {
virtual ~foo() {}
virtual int bar() = 0;
};
struct food : foo {
int bar() final { return 2; }
};
struct fool : foo {
int bar() override { return 3; }
};
int test(food& f) { return f.bar(); }
int test(fool& f) { return f.bar(); }
test(food& f)
用 -O2
編譯後產生的組語是
test(food&):
mov eax, 2
ret
而 test(fool& f)
則產生
test(fool&):
mov rax, QWORD PTR [rdi]
mov rax, QWORD PTR [rax+16]
cmp rax, OFFSET FLAT:fool::bar()
jne .L6
mov eax, 3
ret
.L6:
jmp rax
差別在於前者用了 final
而後者使用 override
,final
可以告訴編譯器 food::bar
不會再被衍生型別覆寫,因此不需要查找 vtable 而直接呼叫 food::bar
。
final
也可以用來敘明類別不會被其他類別繼承。例如:
struct foot final : foo { /* impl */ };
以上可以發現,能套用 devirtualization 的情況,只有在型別已經確定的時候,比如 test(food&)
已經確定接收的是一個 food&
型別,而不是 foo&
這個抽象型別。
Test-Driven-Development (TDD)
假設我們想為上面的 test(food&)
撰寫測試碼,就出現了兩難:我們想改寫 food::bar
的行為以進行測試,然而這樣一來就違背了 final
的語意而造成編譯錯誤;若將介面改成 test(foo&)
則失去了 devirtualization 的最佳化效果。
在目前的標準規格下,或許最簡單的補救方法是使用條件編譯
#if ENABLE_TEST
#define TESTABLE_FINAL override
#else
#define TESTABLE_FINAL final
#endif
struct food : foo {
int bar() TESTABLE_FINAL { return 2; }
};
來讓下面的測試用程式合法
struct foodTest : food {
int bar() override { return 9; }
}
不過 macro 降低了可讀性,條件編譯也需要重新編譯才能測試,或許哪天標準會考慮加入
struct foodTest; // forward declaration
struct food : foo {
int bar() final_except(foodTest) { return 9; }
};
或者改用 attribute 的方式
struct foodTest; // forward declaration
struct food : foo {
[[final_except(foodTest)]]
int bar() { return 9; }
};
通知編譯器允許 foodTest
覆寫 food::bar
。
Written with StackEdit.
留言
張貼留言