一文總結 C++ 常量表達式、constexpr 和 const

来源:https://www.cnblogs.com/tengzijian/p/18018104
-Advertisement-
Play Games

TLDR 修飾變數的時候,可以把 constexpr 對象當作加強版的 const 對象:const 對象表明值不會改變,但不一定能夠在編譯期取得結果;constexpr 對象不僅值不會改變,而且保證能夠在編譯期取得結果。如果一個 const 變數能夠在編譯期求值,將其改為 constexpr 能夠 ...


TLDR

  1. 修飾變數的時候,可以把 constexpr 對象當作加強版的 const 對象:const 對象表明值不會改變,但不一定能夠在編譯期取得結果;constexpr 對象不僅值不會改變,而且保證能夠在編譯期取得結果。如果一個 const 變數能夠在編譯期求值,將其改為 constexpr 能夠讓代碼更清晰易讀。
  2. constexpr 函數可以把運行期計算遷移至編譯期,使得程式運行更快(但會增加編譯時間)。但如果 constexpr 函數中存在無法在編譯期求值的參數,則 constexpr 函數和普通一樣在運行時求值,此時的返回值不是常量表達式。

1. 常量表達式和 constexpr

C++11 中引入了 constexpr 關鍵字。constexpr 是 const expression 的縮寫,即常量表達式

常量表達式是指值不會改變編譯期可以得到結果的表達式。

1.1 特點

  1. 值不會改變(這一點和普通 const 一樣)
  2. 編譯期就能得到結果!(普通 const 不一定保證)

1.2 使用場景

C++ 在一些場景下必須使用常量表達式,比如:

  • 數組大小
  • 整型模板實參(如 std::array<T, N> 的長度參數 N)
  • switch-case 中的 case 標簽
  • 枚舉量的值
  • 對齊規格

1.3 常見的常量表達式

  1. 字面值(如 42)
  2. 用常量表達式初始化的 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 的好處

  1. 編譯器可以保證 constexpr 對象是常量表達式(能夠在編譯期取得結果),而 const 對象不能保證。如果一個 const 變數能夠在編譯期求值,將其改為 constexpr 能夠讓代碼更清晰易讀
  2. constexpr 函數可以把運行期計算遷移至編譯期,使得程式運行更快(但會增加編譯時間)

對於常量表達式(編譯期值已知),編譯器可以進行更多優化,比如放到只讀記憶體中。但這並不是 constexpr 特有的,有的 const 變數也是常量表達式

1.7 小結

  1. 修飾對象的時候,可以把 constexpr 當作加強版的 const:const 對象只表明值不會改變,不一定能夠在編譯期取得結果;constexpr 對象不僅值不會改變,而且保證能夠在編譯期取得結果
  2. constexpr 函數既可以用於編譯期計算,也可以作為普通函數在運行期使用

擴展閱讀

  • 《C++ Primer 第五版》p58,p214,p267

  • 《Effective Modern C++》條款 15:只要有可能使用 constexpr,就使用它


本文作者:Zijian/TENG(微信公眾號:好記性如爛筆頭),轉載請註明原文鏈接:https://www.cnblogs.com/tengzijian/p/18018104
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 在日常的開發中,我們經常能碰見服務端需要主動推送給客戶端數據的業務場景,比如數據大屏的實時數據,比如消息中心的未讀消息,比如聊天功能等等。 本文主要介紹SSE的使用場景和如何使用SSE。 服務端向客戶端推送數據的實現方案有哪幾種? ...
  • 這篇筆記總結自網課DNS解析和優化【渡一教育】 DNS用於將功能變數名稱轉換成IP地址。 特點: DNS解析過程耗費時間長; DNS有本地緩存。 DNS解析完成得到IP地址,這個IP地址會存儲到本地設備,後續再讀這個功能變數名稱會直接返回本地緩存的IP地址。 用戶瀏覽網頁中的DNS解析流程 首先用戶輸入url地 ...
  • 在 Vue 中說到v-bind大多數時候都是想到template中動態綁定script中的響應式數據。但其實在單文件組件(SFC)中, ...
  • Vite腳手架在打包代碼的時候,會把源代碼里對於靜態資源的訪問路徑轉換為打包後靜態資源文件的路徑。動態訪問靜態資源通常導致讀取不到文件,因為源代碼中使用的路徑是src中的,而打包之後靜態資源帶上了文件指紋,代碼中的路徑卻沒有隨之改變。 ...
  • 前言 單例模式是最簡單的一種模式。在Go中,單例模式指的是全局只有一個實例,並且它負責創建自己的對象。單例模式有減少記憶體和系統資源開銷、防止多個實例產生衝突等優點。 因為單例模式保證了實例的全局唯一性,並且只被初始化一次,所以比較適合全局共用一個實例,且只需要被初始化一次的場景,例如資料庫實例、全局 ...
  • 前言 策略模式定義了一系列演算法,並將每個演算法封裝起來,使它們可以互相替換,且演算法的變換不會影響使用演算法的客戶。 在項目開發中,我們經常要根據不同的場景,採取不同的措施,也就是不同的策略。假設我們需要對a、b這兩個整數進行計算,根據條件的不同,需要執行不同的計算方式。我們可以把所有的操作都封裝在同一個 ...
  • 在編程的世界里,我們經常需要對數據進行迴圈處理,常用的兩種方法就是:for迴圈和foreach迴圈。想象你站在一條裝滿寶貝的傳送帶前,你要親手檢查每一件寶貝。使用for迴圈就像是你親手控制傳送帶的速度和方向,而使用foreach迴圈則是傳送帶自動運轉,你只需專註於寶貝本身。 ...
  • Java 方法重載 方法重載 允許在同一個類中定義多個具有相同名稱的方法,但 參數列表 必須不同。 語法: returnType methodName(parameter1, parameter2, ..., parameterN) { // 方法體 } 示例: public class Main ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...