C++中的constexpr的意义与用法

以下内容转载medium, 字体为繁体, 不喜欢勿入, 无奈许多博客的介绍都写的太差, 这位台湾小哥是看过写的最清晰最棒的, 由简入深 !

内容转载https://medium.com/@tjsw/%E6%BD%AE-c-constexpr-constructor-constexpr-operator-overloading-3a11062900ff


Part1

近代的 C++ 中為我們傳統熟悉的語句和修飾子多了非常多的元素,今天就來聊聊 C++11 開始引入的 constexpr 修飾子的用法吧。


constexpr

是 C++11 對於我們已經熟到透的 const 修飾子的一個加強。 const 大家都知道是代表英文中 constant,常數的意思。代表的是被修飾的變數數值編譯期 (compile-time) 已定,也無法再通過語法修改,任何對於標示為常數的變數的嘗試修改都會造成編譯器報錯。

constexpr 擴展了原本對於 const 的限制,並且明確地給了編譯器更多在編譯時期就可以做的計算空間,讓執行期減少更多不必要的計算。以往需要藉由面目全非的 template meta programming 才能完成的編譯期計算現在可以我們最平常的函式語言就能夠完成。
  • 也大大地讓程式的語意更完整,不再只有以前 const 那樣子的硬性文法規定,而更加多元地讓「常數」的語義出現在各個地方,讓開發者遵守。
  • constexpr function 可以用 macro 函式來想像,但是避掉了非常多使用 macro 的困擾,比如定義的展開結尾分號爆炸,忘記對參數加上括號導致不同優先級的運算子攪和在一起…。
  • 更加彈性地,不只是編譯時期的語義遵守和優化。就算當作一般普通函式在別的上下文呼叫也是完全 OK,完全體現出一套語法但依情境做不同事情。
  • Part2

    constexpr 的用法中我們提過一個 C++ 函式只要滿足一些簡單的限制,現代 C++ 編譯器就可以幫你在編譯期算出函式的結果。而我們一直沒有提到的類別的 constructor 以及類別的 member function 成員函式甚至是 operator overloading 運算子重載,他也是一個函式啊!是不是也能加上 constexpr 修飾呢?

    答案當然是可以,不然這篇就不用寫了。

    那麼具體來說 constexpr 加在類別的成員函式們可以幹嘛?就是希望我們開發 者自定義的類別也可以宣告成 constexpr 變數,達到編譯期的運算效果。

    cube() 還是一個標上 constexpr 的函式,代表了裡面的運算元 ©,以及運算 () 都要是編譯時期可知的操作,也就是 c * c * c 必須是一個 constant expression
  • c1 也掛上 constexpr,因此他的初始化操作也必須是一個 constexpr function。
  • 結上所述,我們必須對 CClass 的 constructor,以及 operator() 加上 constexpr 的修飾。

    一個 constructor 畢竟代表了整個類別的性質,掛上 constexpr 的 constructor 就代表使用者自定義的類別是一個字面型別 (literal type)。

    因此整個類別被拿用初始化 constexpr 變數時 (例如 main()c1) ,必須確保除了 constexpr constructor 裡面沒有奇怪的操作,而且所有的成員變數都要被初始化列表 (member initialization list) 以編譯時期可知的值初始化。(或是成員宣告直接賦值也行)

    完整的 CClass 定義應該是這樣的:

    CClass 的 constructor 完全沒有操作,也初始化了所有,也是唯一的成員變數 n_,因此自然就符合了 constexpr 的限制。(可以試試看把 n_ 寫在 constructor 函式體裡面賦值,就會吃 compile error 了)
  • operator*() 就是個成員函式,做了一個 constexpr 物件回來,也很乖巧。
  • 所以我們再度利用組語編譯,也能發現編譯器的確把 c2_6c3_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 的適用範圍再擴展


    本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

    相关文章

    立即
    投稿

    微信公众账号

    微信扫一扫加关注

    返回
    顶部