Wiki
現代C++
本文由簡體中文內容確定性轉換,並受版本化術語表保護。
"現代 C++"通常指 C++11 以及之後的標準引入的一系列寫法和庫工具。它不是把舊 C++ 全部推翻,而是在原有語法之上,提供更安全、更清晰、更適合工程開發的表達方式。
本章按"由淺入深、從常用到進階"的順序組織:先學每天都會用到的語法,再學生命週期和資源管理,接著學回調、資料表達、時間、檔案和併發,最後瞭解模組化編譯。
1.本章例程約定
為了方便你直接複製驗證,本章各小節的"示例程式碼"遵循這些約定:
- 示例程式碼都包含完整
#include。 - 示例程式碼都包含
main函式。 - 示例程式碼可以複製到單個
.cpp檔案中編譯執行。 - 每個示例程式碼下方都給出執行結果。
- 示例儘量一次只增加一個新概念,避免一個例子塞太多東西。
文中的"常見錯誤"用於說明不要這樣寫。為了避免誤複製,錯誤寫法會盡量放在文字說明或短片段中,不當作可執行示例。
2.學習順序
| 階段 | 先解決的問題 | 對應章節 |
|---|---|---|
| 基礎表達 | 少寫重複型別,減少空指標和列舉混用,遍歷更清楚 | auto、nullptr、using、enum class、範圍 for、結構化繫結 |
| 生命週期 | 資源什麼時候釋放,誰擁有物件,大物件怎麼高效傳遞 | constexpr、RAII、智慧指標、右值引用和移動語義 |
| 回撥和資料模型 | 小函式、回撥儲存、引數適配、可能沒有值、多種型別之一 | Lambda、std::function、std::bind、std::optional、std::variant、std::span |
| 系統能力 | 格式化輸出、時間、併發、檔案系統、模組化 | std::format / std::print、std::chrono、併發程式設計、std::filesystem、modules |
現代 C++ 的重點不是"語法更新",而是"把意圖寫清楚"。例如:
nullptr讓空指標和整數0分開。enum class讓不同列舉型別不能隨便混用。- RAII 和智慧指標把資源釋放交給物件生命週期。
- Lambda 讓區域性回撥寫在使用現場。
std::function把不同型別的可呼叫物件統一儲存和傳遞。std::format/std::print讓格式化輸出更簡潔,並保持型別安全。
3.舊寫法和現代寫法的關係
很多現代 C++ 特性都來自一個樸素問題:舊寫法能用,但在複雜場景裡容易出錯。
| 舊寫法 | 主要問題 | 現代寫法 |
|---|---|---|
NULL / 0 表示空指標 |
會和整數過載混淆 | nullptr |
typedef 寫類型別名 |
模板別名不直觀 | using |
| 普通列舉 | 列舉名汙染作用域,可隱式轉整數 | enum class |
手動 new/delete |
提前返回、異常、重複釋放都容易出錯 | RAII、std::unique_ptr、std::shared_ptr |
手動 lock/unlock |
忘記解鎖會死鎖,異常路徑更危險 | std::lock_guard、std::unique_lock |
| 遠處定義普通函式做簡單回撥 | 區域性邏輯被拆散,不能方便攜帶上下文 | Lambda |
| 只用函式指標儲存回撥 | 不能儲存有捕獲的 lambda 和函式物件 | std::function |
| 手寫適配函式調整引數 | 程式碼重複,舊介面適配麻煩 | std::bind 或 Lambda |
| 用特殊值表示失敗 | -1、空字串等魔法值語義不清 |
std::optional |
用 union 表示多種型別 |
訪問錯誤型別會產生未定義行為 | std::variant |
| 手寫時間單位換算 | 秒、毫秒、微秒容易混 | std::chrono |
| 平臺相關檔案 API | Windows/Linux 寫法不同 | std::filesystem |
學習每個特性時,可以按三個問題來理解:
- 舊寫法哪裡容易錯?
- 現代寫法如何把意圖寫進型別或語法裡?
- 在什麼場景下差異才明顯?
比如一個智慧指標示例如果只在 main 裡建立物件然後正常結束,看起來和手動 delete 差不多;一旦出現提前 return、異常、跨函式傳遞,RAII 的價值就會立刻顯現。一個 Lambda 如果隻立刻呼叫,按值捕獲和按引用捕獲都可能沒事;一旦儲存到回撥、執行緒、定時器裡,生命週期差異就會變得非常重要。
4.推薦學習路線
4.1.讓程式碼更清楚
先學習 auto、nullptr、using、enum class、範圍 for 和結構化繫結。這些內容難度不高,但能明顯減少冗長程式碼和低階錯誤。
這一階段重點記住:
auto不是弱型別,只是讓編譯器幫你寫型別。nullptr只表示空指標,不表示整數。enum class預設不和整數混用。- 範圍 for 預設用
const auto&遍歷大物件。 - 結構化繫結適合解包
pair、tuple、結構體和map元素。
4.2.理解生命週期
接著學習 constexpr、RAII、智慧指標和移動語義。這一階段比語法更重要的是思維方式:物件什麼時候建立,什麼時候銷燬,誰擁有它,誰只是借用它。
建議按這個順序理解:
- RAII:資源和物件生命週期繫結。
unique_ptr:獨佔所有權,預設選擇。shared_ptr:確實需要多個所有者時才用。weak_ptr:只觀察,不延長生命週期,用來打破迴圈引用。- 移動語義:把資源轉移出去,避免大物件深複製。
4.3.掌握回撥和資料表達
Lambda、std::function、std::bind 三章要連起來看,但不要混在一起背:
| 工具 | 解決的問題 | 重點 |
|---|---|---|
| Lambda | 在使用現場寫一個小函式 | 捕獲列表、引數、返回值、生命週期 |
std::function |
統一儲存和傳遞不同型別的可呼叫物件 | 型別擦除、回撥成員變數、回撥容器 |
std::bind |
適配已有函式的引數 | 固定引數、調整順序、繫結成員函式 |
std::optional、std::variant、std::span 則分別解決"可能沒有值"、"幾種型別之一"、"借用一段連續資料"的問題。
4.4.走向工程能力
最後學習格式化輸出、時間、併發、檔案系統和 modules。這些內容更接近真實專案:
std::format/std::print比stringstream簡潔,比printf型別安全。std::chrono避免手寫時間單位換算。- 併發程式設計要先保證正確,再考慮速度。
std::filesystem讓路徑、目錄、檔案資訊處理跨平臺。- modules 是 C++20 引入的新編譯模型,目前應先理解概念,再根據工具鏈支援決定是否使用。
5.實戰練習建議
- 學
nullptr:寫兩個過載函式,分別接收int和int*,觀察0、NULL、nullptr的區別。 - 學
enum class:把普通列舉換成列舉類,看看哪些隱式轉換被禁止。 - 學智慧指標:先寫一個提前
return的手動new/delete例子,再改成unique_ptr。 - 學 Lambda:分別用按值捕獲和按引用捕獲,觀察外部變數是否變化。
- 學
std::function:把多個不同 lambda 放進同一個vector,統一執行。 - 學併發:讓兩個 1 秒等待任務先順序執行,再放進兩個執行緒中執行,對比耗時。