後端工坊 2026 年 9 月 26 日

2026-09-26 — 縮減樣板膨脹:把型別無關邏輯搬出樣板函式

primary=https://isocpp.org/blog/2026/09/reducing-cpp-template-bloat-by-factoring-out-type-dependent-portions-of-the primary=https://devblogs.microsoft.com/oldnewthing/20260820-00/?p=112629

縮減樣板膨脹:把型別無關邏輯搬出樣板函式

isocpp.org(轉載 Raymond Chen 於《The Old New Thing》原文)· 2026-09-25

把樣板函式裡與型別完全無關的邏輯,搬進一個獨立、非樣板的函式或結構,讓編譯器只需為每個型別產生一份薄薄的轉接程式碼,其餘邏輯全部共用同一份機器碼——這就是 Raymond Chen 在文章中提出的做法。這篇文章由 isocpp.org 於 2026 年 9 月 25 日轉載重點,原文發表在微軟的《The Old New Thing》部落格。它處理的是一個所有樣板函式庫作者都遇過的老問題:程式碼共用性與具現化膨脹之間的取捨。

背景:邏輯跟型別無關,還是被重新展開一次

C++ 樣板的核心特性是每次具現化都會產生一份全新的函式,即使函式主體有九成邏輯跟樣板參數完全無關,編譯器仍會照著樣板參數把整段邏輯重新展開一次。假設一個資料庫函式庫裡有數十張資料表,每張表對應一個不同的 Table 型別參數,something<Table>() 就會被具現化出數十份幾乎相同的機器碼,差別往往只在走訪欄位時用到的型別。這種重複不只拖慢連結與編譯時間,也直接撐大最終執行檔的體積,在標頭檔實作(header-only)的樣板函式庫上更明顯,因為每個引用它的編譯單元都要重新展開一次同樣的邏輯。Raymond Chen 指出,這類函式常包含大量「準備工作」與「收尾工作」,全部依賴同一個 Database const& 參照,卻完全不依賴樣板參數 Table 本身。

核心改動:用一般函式接手型別無關的部分

文章給出兩種抽法。第一種是把邏輯搬進一個獨立的 struct,建構子與方法上加 __declspec(noinline),強迫編譯器不要把它內聯回樣板函式,否則最佳化又會把邏輯複製回每個具現化版本,前功盡棄。第二種做法更直接:寫一個完全不含樣板參數的一般函式,讓樣板函式只剩下把 Table::Columns() 轉成 std::span<Column>(C++20 引入)再丟給它的一行呼叫。std::span 在這裡扮演型別抹除的角色:不管背後容器是 std::array 還是 std::vector,只要能轉成同一種 std::span<Column>,樣板具現化出來的部分就只剩下那個轉型動作,其餘邏輯全部共用同一份機器碼。

// before:整段邏輯都在樣板裡,每個 Table 都重新展開一次
template<typename Table>
void something(Database const& db)
{
    auto statusIndicator = /* 計算狀態指標 */;
    auto primaryTugboat  = /* 計算主要拖船 */;
    std::vector<Staircase> staircases;

    for (auto&& column : Table::Columns()) {
        /* 用準備好的東西處理每一欄 */
    }
    /* 還有一大段收尾程式碼 */
}
// after:型別無關的部分搬進不含樣板參數的 worker
void somethingWorker(Database const& db, std::span<Column> columns)
{
    auto statusIndicator = /* 計算狀態指標 */;
    auto primaryTugboat  = /* 計算主要拖船 */;
    std::vector<Staircase> staircases;

    for (auto&& column : columns) { /* 同上 */ }
    /* 還有一大段收尾程式碼 */
}

template<typename Table>
void something(Database const& db)
{
    somethingWorker(db, Table::Columns());
}

影響範圍:header-only 函式庫與編譯時間敏感的專案

大型 header-only 樣板函式庫的作者是這個技巧最直接的受益者,因為同一段樣板邏輯會在每個引用它的編譯單元裡各自具現化一次,抽出型別無關邏輯能壓低的正是「編譯單元數乘以具現化次數」這個放大效應。對 CI 編譯時間敏感的專案也該檢查自己的樣板函式裡有多少邏輯其實不觸碰樣板參數,尤其是拿 lambda 當樣板參數的函式,因為每個 lambda 都是獨立型別,邏輯沒抽出來的話膨脹倍數會更誇張。實際要做的事很具體:找出樣板函式裡不碰樣板參數 T 的段落,搬到標了 __declspec(noinline) 的一般函式或 struct 方法裡,或用 std::span、std::string_view 這類型別抹除介面包住差異,讓樣板具現化最終只剩下呼叫那層轉接函式的一行程式碼。

原始來源:isocpp.org;技術細節見 The Old New Thing(Raymond Chen)


End of article
0
Would love your thoughts, please comment.x
()
x