第 18 節

現代C++

0瀏覽次數0訪問次數--跳出率--平均停留

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

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

本章例程約定

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

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

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

學習順序

階段先解決的問題對應章節
基礎表達少寫重複類型,減少空指針和枚舉混用,遍歷更清楚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 讓格式化輸出更簡潔,並保持類型安全。

舊寫法和現代寫法的關係

很多現代 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
平臺相關文件 APIWindows/Linux 寫法不同std::filesystem

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

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

比如一個智能指針示例如果只在 main 裏創建對象然後正常結束,看起來和手動 delete 差不多;一旦出現提前 return、異常、跨函數傳遞,RAII 的價值就會立刻顯現。一個 Lambda 如果隻立刻調用,按值捕獲和按引用捕獲都可能沒事;一旦保存到回調、線程、定時器裏,生命週期差異就會變得非常重要。

推薦學習路線

讓代碼更清楚

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

這一階段重點記住:

  • auto 不是弱類型,只是讓編譯器幫你寫類型。
  • nullptr 只表示空指針,不表示整數。
  • enum class 默認不和整數混用。
  • 範圍 for 默認用 const auto& 遍歷大對象。
  • 結構化綁定適合解包 pairtuple、結構體和 map 元素。

理解生命週期

接着學習 constexpr、RAII、智能指針和移動語義。這一階段比語法更重要的是思維方式:對象什麼時候創建,什麼時候銷燬,誰擁有它,誰只是借用它。

建議按這個順序理解:

  1. RAII:資源和對象生命週期綁定。
  2. unique_ptr:獨佔所有權,默認選擇。
  3. shared_ptr:確實需要多個所有者時才用。
  4. weak_ptr:只觀察,不延長生命週期,用來打破循環引用。
  5. 移動語義:把資源轉移出去,避免大對象深拷貝。

掌握回調和數據表達

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

工具解決的問題重點
Lambda在使用現場寫一個小函數捕獲列表、參數、返回值、生命週期
std::function統一保存和傳遞不同類型的可調用對象類型擦除、回調成員變量、回調容器
std::bind適配已有函數的參數固定參數、調整順序、綁定成員函數

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

走向工程能力

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

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

實戰練習建議

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