跳到正文

Wiki

現代C++

約 7 分鐘閱讀

本文由簡體中文內容確定性轉換,並受版本化術語表保護。

"現代 C++"通常指 C++11 以及之後的標準引入的一系列寫法和庫工具。它不是把舊 C++ 全部推翻,而是在原有語法之上,提供更安全、更清晰、更適合工程開發的表達方式。

本章按"由淺入深、從常用到進階"的順序組織:先學每天都會用到的語法,再學生命週期和資源管理,接著學回調、資料表達、時間、檔案和併發,最後瞭解模組化編譯。

1.本章例程約定

為了方便你直接複製驗證,本章各小節的"示例程式碼"遵循這些約定:

  1. 示例程式碼都包含完整 #include
  2. 示例程式碼都包含 main 函式。
  3. 示例程式碼可以複製到單個 .cpp 檔案中編譯執行。
  4. 每個示例程式碼下方都給出執行結果。
  5. 示例儘量一次只增加一個新概念,避免一個例子塞太多東西。

文中的"常見錯誤"用於說明不要這樣寫。為了避免誤複製,錯誤寫法會盡量放在文字說明或短片段中,不當作可執行示例。

2.學習順序

階段 先解決的問題 對應章節
基礎表達 少寫重複型別,減少空指標和列舉混用,遍歷更清楚 autonullptrusingenum class、範圍 for、結構化繫結
生命週期 資源什麼時候釋放,誰擁有物件,大物件怎麼高效傳遞 constexpr、RAII、智慧指標、右值引用和移動語義
回撥和資料模型 小函式、回撥儲存、引數適配、可能沒有值、多種型別之一 Lambda、std::functionstd::bindstd::optionalstd::variantstd::span
系統能力 格式化輸出、時間、併發、檔案系統、模組化 std::format / std::printstd::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_ptrstd::shared_ptr
手動 lock/unlock 忘記解鎖會死鎖,異常路徑更危險 std::lock_guardstd::unique_lock
遠處定義普通函式做簡單回撥 區域性邏輯被拆散,不能方便攜帶上下文 Lambda
只用函式指標儲存回撥 不能儲存有捕獲的 lambda 和函式物件 std::function
手寫適配函式調整引數 程式碼重複,舊介面適配麻煩 std::bind 或 Lambda
用特殊值表示失敗 -1、空字串等魔法值語義不清 std::optional
union 表示多種型別 訪問錯誤型別會產生未定義行為 std::variant
手寫時間單位換算 秒、毫秒、微秒容易混 std::chrono
平臺相關檔案 API Windows/Linux 寫法不同 std::filesystem

學習每個特性時,可以按三個問題來理解:

  1. 舊寫法哪裡容易錯?
  2. 現代寫法如何把意圖寫進型別或語法裡?
  3. 在什麼場景下差異才明顯?

比如一個智慧指標示例如果只在 main 裡建立物件然後正常結束,看起來和手動 delete 差不多;一旦出現提前 return、異常、跨函式傳遞,RAII 的價值就會立刻顯現。一個 Lambda 如果隻立刻呼叫,按值捕獲和按引用捕獲都可能沒事;一旦儲存到回撥、執行緒、定時器裡,生命週期差異就會變得非常重要。

4.推薦學習路線

4.1.讓程式碼更清楚

先學習 autonullptrusingenum class、範圍 for 和結構化繫結。這些內容難度不高,但能明顯減少冗長程式碼和低階錯誤。

這一階段重點記住:

  • auto 不是弱型別,只是讓編譯器幫你寫型別。
  • nullptr 只表示空指標,不表示整數。
  • enum class 預設不和整數混用。
  • 範圍 for 預設用 const auto& 遍歷大物件。
  • 結構化繫結適合解包 pairtuple、結構體和 map 元素。

4.2.理解生命週期

接著學習 constexpr、RAII、智慧指標和移動語義。這一階段比語法更重要的是思維方式:物件什麼時候建立,什麼時候銷燬,誰擁有它,誰只是借用它。

建議按這個順序理解:

  1. RAII:資源和物件生命週期繫結。
  2. unique_ptr:獨佔所有權,預設選擇。
  3. shared_ptr:確實需要多個所有者時才用。
  4. weak_ptr:只觀察,不延長生命週期,用來打破迴圈引用。
  5. 移動語義:把資源轉移出去,避免大物件深複製。

4.3.掌握回撥和資料表達

Lambda、std::functionstd::bind 三章要連起來看,但不要混在一起背:

工具 解決的問題 重點
Lambda 在使用現場寫一個小函式 捕獲列表、引數、返回值、生命週期
std::function 統一儲存和傳遞不同型別的可呼叫物件 型別擦除、回撥成員變數、回撥容器
std::bind 適配已有函式的引數 固定引數、調整順序、繫結成員函式

std::optionalstd::variantstd::span 則分別解決"可能沒有值"、"幾種型別之一"、"借用一段連續資料"的問題。

4.4.走向工程能力

最後學習格式化輸出、時間、併發、檔案系統和 modules。這些內容更接近真實專案:

  • std::format / std::printstringstream 簡潔,比 printf 型別安全。
  • std::chrono 避免手寫時間單位換算。
  • 併發程式設計要先保證正確,再考慮速度。
  • std::filesystem 讓路徑、目錄、檔案資訊處理跨平臺。
  • modules 是 C++20 引入的新編譯模型,目前應先理解概念,再根據工具鏈支援決定是否使用。

5.實戰練習建議

  1. nullptr:寫兩個過載函式,分別接收 intint*,觀察 0NULLnullptr 的區別。
  2. enum class:把普通列舉換成列舉類,看看哪些隱式轉換被禁止。
  3. 學智慧指標:先寫一個提前 return 的手動 new/delete 例子,再改成 unique_ptr
  4. 學 Lambda:分別用按值捕獲和按引用捕獲,觀察外部變數是否變化。
  5. std::function:把多個不同 lambda 放進同一個 vector,統一執行。
  6. 學併發:讓兩個 1 秒等待任務先順序執行,再放進兩個執行緒中執行,對比耗時。