首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C++ 多任務開發風格大辯論:陳碩 vs 肖舸 —— 從鎖、內存池到異常處理的工程哲學衝突

     編輯:關於C語言

前言:兩位資深工程師的思想碰撞

本文記錄了《0 Bug - C/C++ 商用工程之道》作者肖舸 (網名 TonyXiao) 與資深 C++ 專家陳碩 (muduo 作者、前騰訊高級工程師) 圍繞該書展開的深度技術討論。雙方分別代表**「工程落地派」(肖舸)** 與 **「現代 C++ 規範派」(陳碩)**** 的觀點交鋒,涵蓋 `goto`、宏、自定義鎖、內存池、異常處理、拷貝構造、跨平台適配等核心議題,極具參考價值。

核心爭議點深度解析

1. `goto` 語句:結構化編程的「異端」還是多任務環境的「利器」?

陳碩 (反對派):C++ 中 `goto` 無法跨越變量初始化,編譯器報錯;結構化編程 (`break`/`continue`/`return`/RAII) 足以替代,`goto` 破壞控制流可讀性。

肖舸 (支持派):多任務/並行環境下,資源獲取與釋放呈現嚴格的「二元動作嵌套」 (`lock`/`unlock`, `malloc`/`free`, `new`/`delete`)。深層嵌套時,結構化跳轉 (`break`/`continue`) 無法精確指定落點,**`goto` 能實現精確的層級跳出**,簡化錯誤處理路徑代碼。

共識:`goto` 不是不能用,但**嚴禁濫用**,僅限於「錯誤處理統一出口」等局部受控場景,且需團隊規範約束。

2. 宏與元編程:邪惡的遺產還是函數式編程的萌芽?

陳碩:不喜歡宏,破壞類型安全、調試困難、作用域污染。

肖舸:宏的**編譯期計算固化**、**無類型檢查 (視為優點)**、**運行期結果確定** 特性,符合函數式編程「無副作用、值不可變」思想。嘗試用宏構建「C 語言函數式風格」,利用其天然線程安全特性簡化並發編程。

現代視角:C++17 `constexpr`、C++20 `consteval`、模板元編程、反射提案,已在語言層面提供類型安全的編譯期計算能力,**新代碼應優先使用語言特性而非宏**。

3. 自定義讀寫鎖 vs 系統原語:造輪子的代價

陳碩 (核心反對):

  • **正確性極難保證**:內存模型、亂序執行、Cache 一致性、Memory Barrier、原子操作語義,任何疏漏導致 Race Condition。
  • **歷史教訓**:Double-Checked Locking (DCL) 曾被認為完美,後因亂序執行被證明不安全 (需 `volatile`/Memory Barrier/原子操作修復)。
  • **建議**:直接使用 `pthread_rwlock` / Windows SRWLock / C++17 `shared_mutex` / 成熟庫 (Boost.Thread, folly, muduo)。

肖舸 (工程妥協):

  • **跨平台需求**:項目需同時支持 Windows (客戶端) / Linux (服務端) / ARM Linux (嵌入式),舊平台 (WinXP, 老內核) 無原生讀寫鎖。
  • **統一封裝層**:自行實現一層跨平台鎖抽象,內部根據平台調用原生 API 或回退實現,**業務代碼零感知**。
  • **內存池耦合**:自定義內存池僅支持 `malloc`/`free`,不支持 `new`/`delete` (Placement New 難跨平台),導致對象結構被迫拆分 (數據進池、方法留類),鎖封裝需適配此結構。

結論:**非極端約束 (舊平台、特殊內存模型) 下,嚴禁自造同步原語**。現代項目應基於 C++11/17/20 標準庫或成熟庫構建抽象層。

4. 內存池設計:Placement New 與 Factory 模式的現代解法

針對肖舸「內存池不支持 `new`/`delete`」痛點,陳碩給出標準現代 C++ 方案:

// 1. 內存池接口 (對齊分配)
class MemoryPool {
public:
    void* alloc(size_t n);  // 返回充分對齊地址
    void dealloc(void* p);
};

// 2. Placement New 手動構造/析構
void* mem = pool.alloc(sizeof(BigInt));
BigInt* p = new (mem) BigInt();  // 定位構造
// 使用 p...
p->~BigInt();                    // 手動析構
pool.dealloc(p);

// 3. 封裝為 Factory 模板 (零開銷抽象)
template
class PoolFactory {
    MemoryPool* pool_;
public:
    explicit PoolFactory(MemoryPool* p) : pool_(p) {}
    T* create() {
        void* mem = pool_->alloc(sizeof(T));
        return new (mem) T();
    }
    void destroy(T* p) {
        p->~T();
        pool_->dealloc(p);
    }
};

// 使用
BigInt* p = PoolFactory(&pool).create();
PoolFactory(&pool).destroy(p); // 臨時對象優化掉

此方案**類型安全、支持構造函數參數、RAII 友好、編譯期內聯零開銷**,完美解決「內存池 + 對象語義」矛盾。

5. 拷貝構造與賦值操作符:禁用還是正確實現?

陳碩:包含 `mutex` 的類 (`CMInt`, `CMBool`, `CMutexLock`) **必須刪除拷貝構造/賦值** (`= delete`),否則雙重銷毀、鎖共享導致未定義行為。忽視 Rule of Three/Five 是不合格 C++ 代碼。

肖舸:團隊鐵律**「禁止對象賦值/傳值,只傳指針/引用」**,從規範層面杜絕拷貝場景,故不實現拷貝操作。類比「線程 ID 只能用於 kill,不保存」的防錯誤哲學。

現代最佳實踐:

  1. **值語義類型** (數據結構):正确实現拷貝/移動語義 (Rule of Five)。
  2. **實體/資源管理類型** (含鎖、句柄、IO):**`= delete` 拷貝,實現移動語義** (所有權轉移)。
  3. **多態基類**:`protected` 虛析構,`= delete` 拷貝/移動,防止切片。
  4. 工程規範可輔助,**但語言機制 (刪除函數) 是最後防線**。

6. 析構函數與多線程:對象生命期的終極難題

陳碩:**對象不能用自身的鎖保護自身的銷毀**。析構函數中加鎖解鎖無意義,因為調用析構時對象已進入「死亡狀態」,其他線程持有的指針/引用已成懸空。正確方案:

  • **共享所有權**:`std::shared_ptr` + 自定義刪除器,最後一個引用釋放時安全銷毀。
  • **外部同步**:持有者負責協調所有訪問者退出後再銷毀 (如 `Stop()` 等待線程結束)。
  • **Epoch-based Reclamation / RCU / Hazard Pointers**:無鎖編程進階技術。

肖舸** 承認析構加鎖非正規解法,但作為「防呆」手段:若程序員未正確 `Stop()` 導致殘留線程訪問,析構加鎖可延緩崩潰、賺取日志機會。

7. 異常處理:C++ 的「怪胎」還是 Java 的「常態」?

雙方共識:反對濫用 C++ 異常,理由高度一致:

  • **API 邊界不透明**:頭文件無法聲明所有可能拋出的異常 (無 `throws` 子句),調用者極易漏捕獲導致進程崩潰。
  • **棧展開丟失現場**:未捕獲異常導致 `terminate`,默認無棧回溯信息 (需平台特定 hack 如 `backtrace`/`StackWalker`)。
  • **跨邊界失效**:不能跨進程、跨 DLL、跨網絡傳遞 (NPI/RPC 需錯誤碼)。
  • **資源洩漏風險**:非 RAII 代碼中異常拋出導致洩漏。
  • **控制流隱式跳轉**:類似「無標籤 goto」,難以推理程序狀態。

差異:陳碩認為 Java `try-catch-finally` 機制較優 (finally 保證解鎖);肖舸堅持異常本質是破壞確定性控制流。

工程建議:**庫/框架/系統級代碼禁用異常,應用層業務邏輯可謹慎使用 (配合 RAII),統一在邊界轉為錯誤碼/Result 類型**。

8. 跨平台開發環境:Windows IDE + Linux 交叉編譯實戰

肖舸分享成熟工作流:

  • **編寫/編譯/靜態分析**:Visual Studio (VS2008/2017/2019) + Visual Assist + UltraEdit (鍵盤宏)。
  • **構建/運行/調試**:FTP/SCP 推送源碼 → Linux 服務器 `make` / 交叉編譯鏈 (ARM) → SSH/Putty 運行 → `print` 大法日志分析 (拒絕 GDB/單步調試)。
  • **核心洞見**:**並行系統單步調試無效** (斷點破壞時序),高性能服務器調試依賴**結構化日志 + 事後分析**。
  • **工具鏈堅持**:「合用就好」,拒絕為「耍酷」學 Vim/Emacs,效率優先。

總結:工程無銀彈,語境定選型

這場對話折射出 C++ 開發的核心張力:**語言理想 (現代 C++、RAII、標準庫、類型安全) vs 現實約束 (舊平台、嵌入式資源受限、團隊水平參差、歷史代碼包袱、7×24 高可用)。**

  • **陳碩視角**:站在現代 C++ (C++11/14/17/20) 高地,推崇標準庫、RAII、類型系統、成熟庫,**以語言機制約束人為錯誤**,追求零開銷抽象與正確性保證。
  • **肖舸視角**:深耕嵌入式、電信級服務器、跨平台 (Win/Linux/ARM) 交付一線,**以工程規範、工具鏈、經驗模式解決具體問題**,接受「C with Classes」風格換取可控性與移植性。

**給讀者的啟示**:

  1. **認清項目語境**:新項目、現代編譯器、高性能服務器 → 擁抱現代 C++ (陳碩路徑);舊平台、嵌入式、極致穩定性、團隊混合 → 規範化「C 風格 C++」+ 核心模塊現代化 (肖舸路徑)。
  2. **建立團隊規範**:無論選哪條路,**統一風格、Code Review、靜態分析、CI/CD** 遠比語言特性本身重要。
  3. **持續學習現代 C++**:即使當前受限於舊環境,理解 `placement new`、`shared_ptr`、`memory_order`、`coroutine` 等機制,能讓你在受限環境下設計出更安全的抽象。
  4. **調試觀升級**:高併發系統拋棄單步調試,**構建可觀測性 (Metrics/Logs/Traces)** 才是正道。

這場跨越十年的對話,至今仍是 C++ 工程師必讀的「避坑指南」與「思維升級」教材。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved