TLDR 修飾變數的時候,可以把 constexpr 對象當作加強版的 const 對象:const 對象表明值不會改變,但不一定能夠在編譯期取得結果;constexpr 對象不僅值不會改變,而且保證能夠在編譯期取得結果。如果一個 const 變數能夠在編譯期求值,將其改為 constexpr 能夠 ...
TLDR
- 修飾變數的時候,可以把 constexpr 對象當作加強版的 const 對象:const 對象表明值不會改變,但不一定能夠在編譯期取得結果;constexpr 對象不僅值不會改變,而且保證能夠在編譯期取得結果。如果一個 const 變數能夠在編譯期求值,將其改為 constexpr 能夠讓代碼更清晰易讀。
- constexpr 函數可以把運行期計算遷移至編譯期,使得程式運行更快(但會增加編譯時間)。但如果 constexpr 函數中存在無法在編譯期求值的參數,則 constexpr 函數和普通一樣在運行時求值,此時的返回值不是常量表達式。
1. 常量表達式和 constexpr
C++11 中引入了 constexpr
關鍵字。constexpr 是 const expression 的縮寫,即常量表達式。
常量表達式是指值不會改變且編譯期可以得到結果的表達式。
1.1 特點
- 值不會改變(這一點和普通 const 一樣)
- 編譯期就能得到結果!(普通 const 不一定保證)
1.2 使用場景
C++ 在一些場景下必須使用常量表達式,比如:
- 數組大小
- 整型模板實參(如
std::array<T, N>
的長度參數 N) - switch-case 中的 case 標簽
- 枚舉量的值
- 對齊規格
1.3 常見的常量表達式
- 字面值(如 42)
- 用常量表達式初始化的 const 對象
一個對象(或表達式)是否是常量表達式取決於類型和初始值,如:
int i1 = 42; // i1 不是常量表達式:初始值 42 是字面值,但 i1 不是 const 類型
const int i2 = i1; // i2 不是常量表達式:初始值 i1 不是常量表達式
const int i3 = 42; // i3 是常量表達式:用字面值 42 初始化的 const 對象
const int i4 = i3 + 1; // i4 是常量表達式:用常量表達式 i3 + 1 初始化的 const 對象
const int i5 = getValue(); // 如果 getValue() 是普通函數,則 i5 值要到運行時才能確定,則不是常量表達式
1.4 constexpr 變數
上面的例子可以看出,不能直接判斷一個 const 對象是否是常量表達式:例如 i4 是否是常量表達式取決於 i3 是否是常量表達式,而 i4 又可能用來初始化其他常量表達式。在複雜的系統中,很難一眼看出某個 const 對象是否是常量表達式。
C++11 允許把變數聲明為 constexpr 類型,此時編譯器會保證 constexpr 變數是常量表達式(否則編譯報錯)。換句話說,只要看到 constexpr 類型的變數,則一定能夠在編譯期取得結果,可以用在需要常量表達式的場景。
int i1 = 42;
constexpr int i2 = i1; // constexpr 變數 'i2' 必須由常量表達式初始化。不允許在常量表達式中讀取非 const 變數 'i1'
constexpr int i3 = 42; // i3 是常量表達式
constexpr int i4 = i3 + 1; // i4 是常量表達式
constexpr int i5 = getValue(); // 只有 getValue() 是 constexpr 函數時才可以,否則編譯報錯
1.5 constexpr 函數
constexpr 函數是指能用於常量表達式的函數。
需要強調的是,constexpr 函數既能用於要求常量表達式/編譯期常量的語境,也可以作為普通函數使用。
註意:constexpr 函數不一定返回常量表達式!
只有 constexpr 的所有實參都是常量表達式/編譯期常量時,constexpr 函數的結果才是常量表達式/編譯期常量。只要有一個參數在編譯期未知,那就和普通函數一樣,在運行時計算。
constexpr int sum(int a, int b) {
return a + b;
}
constexpr int i1 = 42;
constexpr int i2 = sum(i1, 52); // 所有參數都是常量表達式,sum 的結果也是常量表達式,在編譯期求值
int AddThree(int i) {
return sum(i, 3); // i 不是常量表達式,此時 sum 作為普通函數使用
}
為了能保證 constexpr 函數在編譯時能隨時展開計算,constexpr 函數隱式內聯。內聯函數和 constexpr 函數不同於其他函數,允許定義多次,但要保證所有的定義一致。正因如此,內聯函數和 constexpr 函數一般定義在頭文件中。
constexpr 限制
因為需要在編譯期求值,所以 constexpr 函數有一些限制:返回類型和所有形參的類型必須是字面值類型(literal type)。除了內置類型,用戶自定義的類也可以是字面值類型,因為它的構造函數和成員函數也可以是 constexpr 函數。
C++11 中 constexpr 函數還有一些額外限制(C++14 沒有這些限制):
- 返回值類型不能是 void
- 函數體內只能有且只有一條 return 語句(但可以用
? :
三目運算符和遞歸) - 如果是類的成員函數,則為隱式 const 成員函數
1.6 使用 constexpr 的好處
- 編譯器可以保證 constexpr 對象是常量表達式(能夠在編譯期取得結果),而 const 對象不能保證。如果一個 const 變數能夠在編譯期求值,將其改為 constexpr 能夠讓代碼更清晰易讀
- constexpr 函數可以把運行期計算遷移至編譯期,使得程式運行更快(但會增加編譯時間)
對於常量表達式(編譯期值已知),編譯器可以進行更多優化,比如放到只讀記憶體中。但這並不是 constexpr 特有的,有的 const 變數也是常量表達式
1.7 小結
- 修飾對象的時候,可以把 constexpr 當作加強版的 const:const 對象只表明值不會改變,不一定能夠在編譯期取得結果;constexpr 對象不僅值不會改變,而且保證能夠在編譯期取得結果
- constexpr 函數既可以用於編譯期計算,也可以作為普通函數在運行期使用
擴展閱讀
-
《C++ Primer 第五版》p58,p214,p267
-
《Effective Modern C++》條款 15:只要有可能使用 constexpr,就使用它
本文作者:Zijian/TENG(微信公眾號:好記性如爛筆頭),轉載請註明原文鏈接:https://www.cnblogs.com/tengzijian/p/18018104