constexpr:編譯期與運行期之間的神祕關鍵字

Scott Meyers在effective modern c++中提到「If there were an award for the most confusing new word in C++11, constexpr would probably win it.」c++

因而可知,constexpr確實是比較難以讓人理解。加之其在C++11和14中的標準略有不一樣,也加重了這種難度。數組

參考幾本經典教材(C++ primer, effective modern C++, a tour of C++)以及藍色大大在知乎上的一些解答,整理出constexpr的用法和注意事項。函數

1.概念,constexpr objects優化

C++ primer中給出的定義是 「常量表達式是指不會改變而且在編譯過程當中就能獲得計算結果的表達式 【1】。」ui

能夠理解爲在const上又加一層限定條件,即const並不限定是編譯期常量仍是運行期常量,而constexpr必須是編譯期常量(在編譯階段獲得結果)spa

舉例以下:設計

衆所周知,array的size是須要在編譯期肯定的,因此當其size不是一個常量表達式時,是沒法經過編譯的。code

int i;
const int size = i;
int arr[size];         //error,size不是常量表達式,不能在編譯期肯定

而若是size是一個constexpr變量,則符合編譯期肯定的條件,能夠經過編譯。對象

constexpr auto size = 10;
int arr[size];                     //OK,size時常量表達式

固然,要定義一個常量表達式的時候,也要確保其右側是常量表達式,不然該處便沒法經過編譯。blog

int i;
constexpr int size = i;  // error,i不能在編譯期肯定

因此用effective modern c++中的一句話總結這一部分就是:

「constexpr objects are const and are initialized with values known during compilation【2】.

 

2. constexpr functions

比起constexpr變量,用constexpr修飾的函數有些更容易混淆的地方。

1) constexpr修飾的函數,當傳入參數是能夠在編譯期計算出來時,產生constexpr變量;

             當傳入參數不能夠在編譯期計算出來時,產生運行期遍歷(constexpr等於不存在)。

所以,沒必要寫兩個函數,若是函數體存在constexpr適用條件,就應該加上constexpr關鍵字。

例如(例子來源【3】):

constexpr int foo(int i) {
    return i + 5;
}

int main() {
    int i = 10;
    std::array<int, foo(5)> arr; // OK,5是常量表達式,計算出foo(5)也是常量表達式
    
    foo(i); // Call is Ok,i不是常量表達式,但仍然能夠調用(constexpr 被忽略)
    
    std::array<int, foo(i)> arr1; // Error,可是foo(i)的調用結果不是常量表達式了
   
}

2) 在C++11和14中的區別

在C++11標準中,對於constexpr修飾的函數給了及其苛刻的限定條件:函數的返回值類型及全部形參的類型都是字面值類型,並且函數體內必須有且只有一條return語句【1】

這個條件顯然是太苛刻了,以致於不少在constexpr的操做都要藉助?:表達式,遞歸等辦法實現。

在C++14中,放寬了這一限定,只保留了「函數的返回值類型及全部形參的類型都是字面值類型」,也就是說,這些值都在編譯期能肯定了就行

 

3. constexpr class(字面值常量類)

built-in類型是字面值常量,可是有時須要自定義類型也做爲字面值常量,這時候就需須要將constexpr修飾構造函數

字面值常量類必須至少提供一個constexpr構造函數。

例如:

class Point {
public:
    constexpr Point(double xval = 0, double yval = 0): x(xval), y(yval) { }
    constexpr double getX() const {return x;}
    constexpr double getY() const {return y;}
private:
    double x,y;
};

當這樣定義一個類後,即可以將Point類型的對象定義爲字面值常量。即:

constexpr Point p1(9.4, 27,7);
constexpr Point p2(28.8, 5.3);

constexpr
Point midpoint(const Point& p1, const Point& p2) {
    return {p1.getX() + p2.getX() / 2, p1.getY() + p2.getY() / 2} ;
}
 
constexpr auto mid = midpoint (p1, p2);

上述例子中,p1,p2均爲字面值常量,midpoint爲constexpr修飾的函數,因此求取mid的整個過程均在編譯期就能夠完成,軟件運行的時間天然會大大減小。

至此關於constexpr的三個主要用途(constexpr變量,constexpr修飾函數,constexpr修飾構造函數)就總結完畢,下面是一些注意事項。

 

注意事項1: 不少人(包括我本身)在gcc中驗證數組大小必須在編譯期指定的例子時發現:

若是array定義在主函數內,即便給定的不是一個常量表達式,也能夠經過編譯。這差點顛覆了個人認知。。。

藍色大大在知乎答案【4】中解釋了這一點,實際上是C99中的variable length array。在全局變量中不能使用(沒法分配內存),在局部變量中可使用,細節能夠參考那份解答。

注意事項2:constexpr這麼複雜,到底爲何要用? 

其實第一仍是爲了效率。效率是C++的設計哲學之一,編譯期能夠肯定的東西,即可以提醒編譯期優化,也可能存放在read-only memory中

第二就是這樣聲明的constexpr變量即可以用在諸如上述數組長度指定,還有包括模板參數,case標籤等場合,會便於使用【5】

 

參考資料:

1. Stanley B. Lippman / Josée Lajoie / Barbara E. Moo,  C++ Primer 中文版(第 5 版)[M].  電子工業出版社,2013

2. Meyers S. Effective Modern C++[M]. O'Reilly, 2014.

3. 藍色在知乎問題「C++ const 和 constexpr 的區別?」中的解答: https://www.zhihu.com/question/35614219

4. 藍色在知乎問題「constexpr和const數組的區別?」中的解答: https://www.zhihu.com/question/29662350/answer/45192834

5. Stroustrup B. A Tour of C++[M]. Addison-Wesley Longman, Amsterdam, 2013.

相關文章
相關標籤/搜索