Mudo C++網路庫第十一章學習筆記

self發表於2018-10-25

反思C++物件導向與虛擬函式

  • C++語言學習可以看《C++ Primer》這本書;
  • 在C++中進行物件導向程式設計會遇到其他語言中不存在的問題, 其本質原因是C++ class是值語義, 而非物件語義;

樸實的C++設計

  • 實用當頭, 樸實為貴, 好用才是王道;
  • C++ 是一門(最)複雜的程式語言, 語言雖複雜, 不代表一定要用複雜的方式來使用它;
  • 不一定非得有基類和派生類的設計才是好設計;
  • 一個類代表一個概念;
  • 讓程式碼保持清晰, 給我們帶來了顯而易見的好處;
  • 不要因為某個技術流行而去用它, 除非它確實能降低程式的複雜度;
  • 在C++這樣需要自己管理記憶體和物件生命期的語言裡, 大規模使用物件導向、繼承、多型多是自討苦吃;

C++編譯器ABI的主要內容主要包括幾個方面:

  • 函式傳遞的方式, 比如x86-64用暫存器來傳函式的前4個整數引數;
  • 虛擬函式的回撥方式, 通常是vptr/vtbl機制, 然後用vtbl[offset]來呼叫;
  • struct和class的記憶體佈局, 通過偏移量來訪問資料成員;
  • name mangling;
  • RTTI 和異常處理的實現;
  • 避免使用虛擬函式作為庫的介面;
    • 因為這樣會給保持二進位制相容性帶來很大的麻煩;
  • JAVA的庫都是.jar檔案;

虛擬函式作為庫的介面的兩大用途

  • 呼叫;
  • 回撥, 也就是事件通知, 比如網路庫的連線建立, 資料到達, 連線斷開等;
  • 混合使用;

iostream的用途與侷限

  • C++ iostream的主要作用是讓初學者有一個方便的命令列輸入輸出試驗環境, 在真實的專案中很少用到iostream;
    • 不用花大量的精力在iostream的格式化與manipulator(格式操作符)上;
  • glibc定義的getline函式來讀取不定長的行;
    • 返回的是malloc()分配的記憶體, 要求呼叫端自己free()掉;
  • iostream不是執行緒安全的;
  • 用到了多重繼承和虛擬繼承;
  • Google Protobuf是一種高效的網路傳輸格式, 它用一種協議描述語言來定義訊息格式, 並且自動生成序列花程式碼;
  • C++的強大之處在於抽象不以效能損失為代價;
  • 資料抽象(data abstraction)是與面向物件導向(object-oriented)並列的一種程式設計正規化(programming pragadigm);

資料抽象所需的語言設施

  • 支援資料聚合(data aggregation);
  • 全域性函式與過載;
  • 成員函式與private資料;
  • 拷貝控制(copy control);
  • C++ class是值語義, copy control是實現深拷貝的必要手段, 而且ADT用到的資源只涉及動態分配的記憶體, 所以深拷貝是可行的;
  • 操作符過載;
  • 效率無損, 抽象不代表低效;
    • 在C++中, 提高抽象的層次並不會降低效率;
  • 模板與泛型;
  • 資料抽象是C++的重要抽象手段, 適合封裝資料, 它的語義簡單, 容易使用;
  • 大多數class都是物件語義;

C++經驗談

  • 練從難處練, 用從易處用;
  • 軟體開發一定要時刻注意減少不必要的複雜度;
  • 作為應用程式的開發者, 對技術的運用要明智, 不要為了瞭解難度係數為10的問題而去強攻難度係數為100的問題, 這就本末倒置了;
  • 用異或來交換變數是錯誤的;
  • 未定義的行為, 在C/C++語言的一條語句中, 一個變數的值只允許改變一次(像x = x++這種程式碼都是未定義行為, 因為x有兩次寫入);
  • 現在的編譯器會把std::reverse()這種簡單函式自動內聯展開, 生成出來的優化彙編程式碼和其他程式碼一樣快;
  • 不要猜(guess), 要測(benchmark);
  • 不要過載全域性::operator new();
    • 按現代C++的手法(RAII)來管理記憶體, 很難遇到什麼記憶體方面的錯誤;
  • 記憶體管理的基本要求:
    • 記憶體管理的基本要求是不重不漏 -- 既不重複delete, 也不漏掉delete;
    • new/delete配對, 不僅是個數相等, 還隱含了new和delete的呼叫本身要匹配, 不要東家借的東西西家還;
    • 用系統預設的malloc()分配的記憶體要交給系統預設的free()去釋放;
    • 用系統預設的new表示式建立的物件要交給系統預設的delete表示式去析構並釋放;
    • 用系統預設的new[]表示式建立的物件要交給系統預設的delete[]表示式去析構並釋放;
    • 用系統預設的::operator new()分配的記憶體要交給系統預設的::operator delete()去釋放;
    • 用placement new建立的物件要用placement delete(為了表述方便, 姑且這麼說吧)去析構(其實就是直接呼叫解構函式);
    • 從某個記憶體池A分配的記憶體要還給這個記憶體池;
    • 如果定製new/delete, 那麼要按規矩來;
      • 檢查程式碼中的記憶體錯誤;
      • 優化效能;
      • 獲得記憶體使用的統計資料;
  • 指令碼語言直譯器程式碼:
    • Python的程式碼很好讀;
  • C語言的static關鍵字的兩種用法:
    • 用於函式內部修飾變數, 即函式內的靜態變數; 使用靜態變數的函式一般是不可重入的, 也不是執行緒安全的;
    • 用在檔案級別(函式體之外), 修飾變數或函式, 表示該變數或函式只能在本檔案可見, 其他檔案看不到, 也訪問不到該變數或函式(interal linkage);
  • C++語言的static關鍵子的四種用法:
    • static關鍵字又有了兩種新用法: 用於修飾class的資料成員, 即所謂的靜態成員, 這種資料成員的生存期大於class的物件(實體/instance);
    • 靜態成員是每個class有一份, 普通資料成員是每個instance(例項)有一份, class variable(類變數)和instance variable(例項變數);
    • 用於修飾class的成員函式, 即所謂的靜態成員函式, 靜態成員函式只能訪問class variable和其他靜態程式函式, 不能訪問instance variable或instance method;
  • 協議設計是網路程式設計的核心:
    • 訊息格式: XML, JSON, Protobuf, 難的是訊息內容;
  • 網路程式設計的三個層次:
    • 讀過教程和文件, 做過練習 -- 讀過《UNIX網路程式設計》《TCP/IP詳解》並理解TCP/IP協議, 讀過本系統的manpage;
    • 熟悉本系統TCP/IP協議棧的脾氣;
      • 有可能出現TCP自連線(self-connection), 程式應該有所準備;
      • Linux核心會有bug, 比如某種TCP擁塞控制演算法曾經出現TCP window clamping(視窗錯位)bug, 導致吞吐量暴跌, 可以選用其他擁塞控制演算法來繞開(work around)這個問題;
    • 自己寫過一個簡單的TCP/IP stack;
  • TCP網路程式設計有三個例子最值得學習研究: 分別是echo, chat, proxy都是長連線協議;
    • proxy的作用: 連線的管理更加複雜, 既要被動接受連線, 也要主動發起連線, 既要主動關閉連線, 也要被動關閉連線, 還要考慮兩邊速度不匹配;
  • 三本必看的書:
    • 談到Unix程式設計和程式設計程式設計, W.Richard Stevens是個繞不開的人物;
      • [APUE]、兩卷《UNIX網路程式設計》、三卷《TCP/IP詳解》;
      • [UNPv2]其實跟網路程式設計關係不大, 是[APUE]在多執行緒和程式間通訊(IPC)方面的補充;
      • 《TCP/IP詳解》三卷, 用處不同, 應該區別對待;
    • 第一本《TCP/IP Illustrated, Vol. 1: The Protocols》(TCP/IP詳解);
      • 從使用者(程式設計師)的角度, 以tcpdump為工具, 對TCP協議抽絲剝繭, 娓娓道來;
      • TCP作為一個可靠的傳輸層協議, 其核心有三點:
        • Positive acknowledgement with retransmission(對重傳的積極響應) -- 可靠性;
        • Flow control using sliding window(包括Nagle演算法等) -- 提高吞吐量;
        • Congestion(擁塞) control(包括slow start、congestion avoidance、fast retransmit) -- 防止過載造成丟包;
      • TCP像是一個自適應的節流閥, 根據管道的擁堵情況自動調整閥門的流量;
    • 第二本《Unix Network Programming, Vol.1:Networking API》統稱UNP;
      • UNP是Sockets API的權威指南;
      • 網路程式設計遠不是使用那十幾個Sockets API那麼簡單, 一定要熟悉TCP/IP協議及其外在表現(比如開啟和關閉Nagle演算法對收發包延時的影響);
      • UNP中問版《UNIX網路程式設計》翻譯得相當好, 譯者楊繼張先生是真懂網路程式設計的;
      • UNP很詳細, 面面俱到, UDP、TCP、IPv4、IPv6都講到了;
      • 講得太詳細, 重點不夠突出;
        • 在具備基礎之後, 學習任何新東西, 都要抓住主線, 突出重點, 對於關鍵理論的學習, 要集中精力, 速戰速決;
        • 作者是先看的TCPv1, 花了大約兩個月的時間, 然後再讀UNP和APUE;
      • 第三本《Effective TCP/IP Programming》;
        • 這本書屬於專家經驗總結類的書籍;
      • 還值得一看的書:
        • 《TCP/IP Illustrated, Vol.2: The Implementation》, 稱為TCPv2;
        • 工作中大可以把IP視為host-to-host的協議;
        • 《Pattern-Oriented Software Architecture Volume 2: Patterns for Concurrent and Networked Objects》, 簡稱POSA2;
          • 這本書總結了開發併發網路服務程式的模式, 是對UNP很好的補充;
          • POSA2強調模組化, 網路通訊交給library/framework去做, 程式設計師寫程式碼只關注業務邏輯(這是非常重要的思想);
          • 這本書對深入理解常用的event-driven網路庫(libevent, Java Netty, Java Mina, Perl POE, Python Twisted)也很有幫助;
          • POSA2的程式碼是示意性的, 思想很好, 細節不佳;C++程式碼沒有充分考慮資源的自動化管理(RAII);
  • 很多企業內部使用C++來構建自己的分散式系統基礎架構, 並且有替換Java開源實現的趨勢;
  • 學習C++只需要讀一本大部頭《The C++ Programming Language》或《C++ Primer》;
    • 《C++ Primer》的主要內容是精解C++語法(syntax)與語意(semantics)並介紹C++標準庫的大部分內容(含STL);
  • C++的開源庫: Google的Protobuf, leveldb, PCRE的C++封裝, 還有就是作者的muduo庫;
  • 如有時間可以讀讀Chromium中基礎庫原始碼, 在讀Google開源的C++程式碼時要連註釋一起細讀;
  • 不建議一開始就讀STL或Boost的原始碼, 因為編寫通用的C++模板庫和編寫C++應用程式的知識體系相差很大;
  • 《Effective C++中文版》,《泛型程式設計與STL》, 《C++程式設計規範》;
  • 避免寫出依賴於函式實參求值順序的程式碼, C++操作讀的優先順序、結合性與表示式的求值順序是無關的;
  • Google的C++程式設計規範和LLVM程式設計規範;

相關文章