Boost 實作變異 (Boost 實作變化)
介面與實作分離
boost.org 函式庫組件的介面規格(以及一般高品質軟體)在概念上與這些介面的實作是分開的。這一點可能並不明顯,尤其當一個組件完全在標頭檔中實作時,但這種介面與實作的分離總是預設存在的。從軟體設計、可移植性和標準化的角度來看,介面才是重要的,而實作只是細節。
Dietmar Kühl,boost.org 的早期貢獻者之一,評論道:「主要的貢獻是介面,它通過一個實作來增強,證明了實現相應類別的可能性,並提供了一個免費的實作。」
實作變異 (實作變化)
可能需要一個介面的多個實作,以適應平台相依性或效能取捨。平台相依性的例子包括編譯器缺陷、檔案系統、執行緒機制和圖形使用者介面。效能取捨的典型例子是一個使用大量記憶體的快速實作,相對於一個使用較少記憶體的較慢實作。
Boost 函式庫通常使用一個設定標頭檔,boost/config.hpp,來捕捉編譯器和平台相依性。雖然並非強制使用 boost/config.hpp,但它是處理簡單設定問題的首選方法。
Boost 政策
Boost 的政策是避免介面規格中的平台相依性變化,但提供可在廣泛平台和應用程式上使用的實作。這意味著 Boost 函式庫將使用下述技術來處理平台相依性。
Boost 對於旨在提升效能的實作變異的政策是避免它們,除非效益遠遠超過全部成本。「全部成本」一詞旨在包含有形的成本,例如額外的維護,以及無形的成本,例如增加使用者理解的難度。
提供實作變異的技術
可以使用幾種技術來提供實作變異。每種技術都適用於某些情況,而不適用於其他情況。
單一通用實作
第一種技術是根本不提供實作變異。而是提供單一的通用實作,並放棄所有其他技術所隱含的複雜性增加。
**適用於:**當可以編寫一個在各種平台上都具有合理效能的單一可移植實作時。尤其適用於替代實作僅在深奧的方面有所不同的情況。
**不適用於:**當實作需要平台特定功能時,或者當存在多個效能特性差異很大的實作時。
Beman Dawes 曾說:「在設計討論中,有些實作方式常被認為比其他方式快得多,但實際測試時間卻發現沒有顯著差異。我們學到的是,雖然演算法的差異可能會戲劇性地影響速度,但程式碼的差異,例如將類別從虛擬成員改為非虛擬成員,或移除一層間接層,不太可能造成任何可測量的差異,除非是在內部迴圈的深處。即使在內部迴圈中,現代 CPU 也經常以相同的時脈週期數執行這些相互競爭的程式碼序列!一個通用的實作方式通常就足夠了。」
或者如同 Donald Knuth 所說:「過早最佳化是萬惡之源。」(計算機概論,第 6 卷,第 4 期,第 268 頁)。
巨集 (Macros)
雖然巨集的缺點眾所周知,但在某些情況下,巨集仍然是首選的解決方案
- 透過 #include 防護避免重複包含標頭檔。
- 將次要的組態資訊從組態標頭檔傳遞到其他檔案。
適用於:用於編譯時期的小幅變化,否則安裝、使用或維護成本高昂或容易混淆。更適合用於函式庫組件內部和組件之間的溝通,而不是與函式庫使用者溝通。
不適用於:如果其他技術可以達成相同目的。
為了最大限度地減少巨集的負面影響
- 僅在巨集明顯優於其他技術時才使用它們。它們應該被視為最後手段。
- 名稱應全部大寫,並以命名空間名稱開頭。這將最大限度地減少名稱衝突的可能性。例如,名為 foobar.h 的 Boost 標頭檔的 #include 防護可能會被命名為 BOOST_FOOBAR_H。
個別檔案
一個函式庫組件可以有多種變體,每個變體都包含在它自己的個別檔案中。在安裝時,最合適變體的檔案會被複製到適當的 include 或 implementation 目錄中。
在 Boost 函式庫中提供這種方法的方式,是將特製的實作方式作為個別檔案包含在 .ZIP 發行檔中的個別子目錄中。例如,名為 foobar 的函式庫的 .ZIP 發行檔中的結構,該函式庫同時具有預設和特製的變體,可能看起來像這樣
foobar.h // The default header file foobar.cpp // The default implementation file readme.txt // Readme explains when to use which files self_contained/foobar.h // A variation with everything in the header linux/foobar.cpp // Implementation file to replace the default win32/foobar.h // Header file to replace the default win32/foobar.cpp // Implementation file to replace the default
適用於:當不同的平台需要不同的實作方式時,或者當可能的實作方式之間存在顯著的效能差異時。
不適用於:在同一個安裝中使用多個變體是有意義的情況下。
個別組件
不要使用單個組件的幾個實作變體,而是提供幾個個別的組件。例如,Boost 函式庫目前提供 scoped_ptr
和 shared_ptr
類別,而不是單個以參數區分兩種情況的 smart_ptr
類別。有幾種方法可以進行組件選擇
- 程式設計師在編碼期間硬編碼 (hardwired)。
- 由程式設計師編寫的執行時期邏輯選擇(以一些額外的空間、時間和程式複雜性來換取在執行時期選擇實作方式的能力)。
適用於:當變體的介面不同時,並且同時使用多個變體是合理的情況下。當需要執行時期選擇實作方式時。
不適用於:當變體是資料類型、特性或特化變體時,透過將組件設為模板可以更好地處理這些情況。當最好透過程式外部的某些設定或安裝機制來選擇變體時,也不適用。因此,通常不適合用於應對平台差異。
注意事項:有一種相關的技術,其中介面被指定為抽象(純虛擬)基類(或介面定義語言),並且實作選擇被傳遞給某些第三方,例如動態連結函式庫或物件請求代理。雖然這是一種強大的技術,但它超出了本次討論的範圍。
基於模板的方法
將類別或函式轉換為模板通常是處理變化的一種優雅方式。基於模板的方法提供了最佳的空間和時間效率,但作為回報,實現的選擇被限制在編譯時。
重要的模板技術包括:
- 資料類型參數化。這允許單個組件對各種資料類型進行操作,也是模板最初被發明的原因。
- 特性參數化。如果參數化很複雜,將各個方面捆綁到一個特性輔助類別中,可以在隱藏繁瑣細節的同時允許很大的變化。C++ 標準函式庫提供了幾個這種慣用法的例子,例如
iterator_traits<>
(24.3.1 lib.iterator.traits) 和char_traits<>(21.2 lib.char.traits)。 - 特化。模板參數可以用於專門選擇特化的目的。例如
SomeClass<fast> my_fast_object; // fast and small are empty classes SomeClass<small> my_small_object; // used just to select specialization
適用時:當需要變化是由於資料類型或特性,或者是與效能相關的,例如在幾個演算法之間進行選擇,並且當程式可能會合理地使用多個變體時。
不適用時:當變體的介面不同,或者當變體的選擇最好由程式本身以外的某種機制完成時。因此,通常不適合處理平台差異。