在 constexpr 的用法中我們提過一個 C++ 函式只要滿足一些簡單的限制,現代 C++ 編譯器就可以幫你在編譯期算出函式的結果。而我們一直沒有提到的類別的 constructor 以及類別的 member function 成員函式甚至是 operator overloading 運算子重載,他也是一個函式啊!是不是也能加上 constexpr
修飾呢?
答案當然是可以,不然這篇就不用寫了。
那麼具體來說 constexpr
加在類別的成員函式們可以幹嘛?就是希望我們開發 者自定義的類別也可以宣告成 constexpr
變數,達到編譯期的運算效果。
#include <cstdio>constexpr int sq(int n) { return n * n; }constexpr CClass cube(const CClass &c) { return c * c * c; }int main()
{
constexpr CClass c1(sq(2));
constexpr CClass c2_6 = cube(c1);
constexpr CClass c3_6 = cube(CClass(sq(3))); static_assert(c2_6.n_ == 64, "!!!");
static_assert(c3_6.n_ == 729, "!!!");
}
如果一切順利的話,我們應該要可以算出 c2_6
代表 2 的 6 次方,c3_6
代表 3 的 6 次方,並且最後通過 printf
印出來。
我們觀察一下上面的半殘代碼:
- 上面的
cube()
涉及了對 CClass 物件的「乘法」,很明顯這裡我們需要實作CClass
的乘法 operator overloading。 cube()
還是一個標上constexpr
的函式,代表了裡面的運算元 (c),以及運算 (*) 都要是編譯時期可知的操作,也就是c * c * c
必須是一個 constant expressionc1
也掛上constexpr
,因此他的初始化操作也必須是一個 constexpr function。
結上所述,我們必須對 CClass
的 constructor,以及 operator*()
加上 constexpr
的修飾。
一個 constructor 畢竟代表了整個類別的性質,掛上 constexpr 的 constructor 就代表使用者自定義的類別是一個字面型別 (literal type)。
因此整個類別被拿用初始化 constexpr 變數時 (例如 main()
的 c1
) ,必須確保除了 constexpr constructor 裡面沒有奇怪的操作,而且所有的成員變數都要被初始化列表 (member initialization list) 以編譯時期可知的值初始化。(或是成員宣告直接賦值也行)
完整的 CClass 定義應該是這樣的:
class CClass
{
public:
constexpr explicit CClass(int n) : n_(n) {}
constexpr CClass operator*(const CClass &rhs) const {
return CClass(n_ * rhs.n_);
}
int n_;
};
CClass
的 constructor 完全沒有操作,也初始化了所有,也是唯一的成員變數n_
,因此自然就符合了constexpr
的限制。(可以試試看把 n_ 寫在 constructor 函式體裡面賦值,就會吃 compile error 了)operator*()
就是個成員函式,做了一個constexpr
物件回來,也很乖巧。
所以我們再度利用組語編譯,也能發現編譯器的確把 c2_6
, c3_6
在編譯時期就算出來了。
.section __TEXT,__const
.p2align 2 ## @_ZZ4mainE2c1
__ZZ4mainE2c1:
.long 4 ## 0x4.p2align 2 ## @_ZZ4mainE4c2_6
__ZZ4mainE4c2_6:
.long 64 ## 0x40.p2align 2 ## @_ZZ4mainE4c3_6
__ZZ4mainE4c3_6:
.long 729 ## 0x2d9
也可以不要編譯期算
constexpr CClass c2_6 = cube(c1);
constexpr CClass c3_6 = cube(CClass(sq(3)));
CClass c88(88);static_assert(c2_6.n_ == 64, "!!!");
static_assert(c3_6.n_ == 729, "!!!");printf("%d %d %d\n", c2_6.n_, c3_6.n_, c88.n_);
就像 constexpr function 一樣,根據我們在什麼時候使用,就可以編譯期算,或是執行期才算。比如上面這個 c88
,他就不會是一個在 TEXT 段的常數。而是執行期呼叫 printf()
的時候才把 n_
從 register 裡面拿出來壓進 stack 裡呼叫 printf()
。
今天的內容很簡單,就是再把 constexpr
的適用範圍再擴展,同學們可以試著更改增減看看 CClass
的各個定義看有什麼結果 XD。