瞭解如何使用 C++ 程式庫模組匯入 C++ 標準程式庫。 這會導致更快的編譯速度,而且比使用標頭檔、標頭單元或預先編譯的標頭(PCH)更加穩健。
在本教學課程中,瞭解:
- 如何從命令列將標準程式庫匯入為模組。
- 模組的效能和可用性優勢。
- 兩個標準庫模塊
std以及std.compat它們之間的區別。
先決條件
本教學課程需要 Visual Studio 2022 17.5 或更新版本。
標準程式庫模組簡介
標頭檔語意可能會根據巨集定義、包含它們的順序而變更,而且它們會減慢編譯速度。 模組解決了這些問題。
現在可以將標準程式庫匯入為模組,而不是匯入一堆標頭檔案。 這比包含標頭檔、標頭單元或預先編譯標頭(PCH)更快且更穩健。
C++23 標準程式庫引進了兩個具名模組: std 和 std.compat:
-
std匯出 C++ 標準函式庫命名空間std中定義的宣告和名稱,例如std::vector. 它還匯出 C 包裝器標頭的內容,例如<cstdio>和<cstdlib>,這些標頭提供了像std::printf()這樣的功能。 不會匯出全域命名空間中定義的 C 函數,例如::printf()。 這改善了包含 C 包裝器標頭的情況,例如<cstdio>也 包括 C 標頭文件,例如stdio.h,它們引入了 C 全域命名空間版本。 如果您匯入std,這不是問題。 -
std.compat匯出所有std內容,並新增 C 執行階段全域命名空間,例如::printf、 、::fopen::size_t::strlen、 等。 該std.compat模塊使使用引用全局命名空間中許多 C 運行時函數/類型的代碼庫變得更加容易。
當您使用 import std; or import std.compat; 時,編譯器會匯入整個標準程式庫,並比導入單一標頭檔更快。 例如,用import std;(或import std.compat)引入整個標準庫,比#include <vector>更快。
因為具名模組不會公開巨集,所以當您匯入std或std.compat時,像assert、errno、offsetof、va_arg等巨集都無法使用。 如需因應措施,請參閱標準程式庫具名模組注意事項。
關於 C++ 模組
標頭檔是 C++ 中原始檔之間共用宣告和定義的方式。 在標準函式庫模組出現之前,您會使用指令如 #include <vector> 來包含所需的標準函式庫部分。 標頭檔很脆弱且難以撰寫,因為它們的語意可能會根據您包含它們的順序或是否定義某些宏而變化。 它們也會減慢編譯速度,因為它們會由包含它們的每個來源檔案重新處理。
C++20 引進了稱為 模組的新式替代方案。 在 C++23 中,我們能夠利用模組支援來引入命名模組來代表標準庫。
與頭檔一樣,模組可讓您在來源檔案之間共用宣告和定義。 但與頭檔不同的是,模組並不脆弱,而且更容易撰寫,因為它們的語義不會因巨集定義或匯入它們的順序而改變。 編譯器處理模組的速度比處理 #include 檔案快得多,並且在編譯時也使用更少的記憶體。 具名模組不會公開巨集定義或私人實作詳細資料。
本文示範取用標準程式庫的新且最佳方式。 如需取用標準程式庫替代方式的詳細資訊,請參閱 比較標頭單位、模組和預先編譯的標頭。
匯入標準程式庫 std
下列範例示範如何使用命令列編譯器將標準程式庫取用為模組。 如需如何在 Visual Studio IDE 中執行此動作的相關資訊,請參閱 建置 ISO C++23 標準程式庫模組。
陳述式 import std; 或 import std.compat; 將標準程式庫匯入您的應用程式。 但首先,您必須將名為模組的標準函式庫編譯為二進位形式。 下列步驟示範如何操作。
範例:如何建置和匯入 std
開啟 VS 的 x86 本機工具命令提示字元:從 Windows 的「開始」 功能表中,鍵入 x86 native ,提示應該會出現在應用程式清單中。 請確定提示適用於 Visual Studio 2022 17.5 版或更新版本。 如果您使用錯誤版本的提示,就會收到錯誤。 本教學中使用的範例適用於 CMD 殼層。
建立目錄,例如
%USERPROFILE%\source\repos\STLModules,並使其成為目前目錄。 如果您選擇沒有寫入權限的目錄,則在編譯期間會發生錯誤。使用下列指令編譯
std具名模組:cl /std:c++latest /EHsc /nologo /W4 /c "%VCToolsInstallDir%\modules\std.ixx"如果您收到錯誤,請確定您使用的是正確版本的命令提示字元。
使用您想要與匯入建置模組的程式碼相同的編譯器設定來編譯
std具名模組。 如果您有多專案解決方案,您可以編譯名為 module 的標準程式庫一次,然後使用/reference編譯器選項從所有專案中參照它。使用上一個編譯器命令,編譯器輸出兩個檔案:
-
std.ifc是編譯器查閱以處理import std;陳述式的具名模組介面的編譯二進位表示法。 這是僅在編譯時使用的產物。 它不隨附您的應用程式。 -
std.obj包含命名模組的實作。 當您編譯範例應用程式時,新增至std.obj命令列,以靜態方式將您從標準程式庫使用的功能連結至應用程式。
此範例中的命令列切換參數為:
開關 Meaning /std:c++latest使用最新版本的 C++ 語言標準和函式庫。 雖然 下 /std:c++20提供模組支援,但您需要最新的標準程式庫才能取得標準程式庫具名模組的支援。/EHsc使用 C++ 例外處理,但標示為 extern "C"的函數除外。/W4通常建議使用 /W4,特別是對於新專案,因為它會啟用所有層級 1、層級 2、層級 3 和大多數層級 4 (參考性) 警告,這可協助您及早發現潛在問題。 本質上,它提供了類似於 lint 的警告,幫助確保程式碼中難以發現的缺陷盡可能少。/c編譯而不連結,因為此時我們只是在建置二進位命名模組介面。 您可以使用下列參數來控制物件檔名及具名模組介面檔名:
-
/Fo設定物件檔的名稱。 例如:/Fo:"somethingelse"。 依預設,編譯器會使用與您正在編譯的模組原始檔 (.ixx) 相同的目標檔名稱。 在此範例中,物件檔案名稱預設為std.obj,因為我們正在編譯模組檔案std.ixx。 -
/ifcOutput設定具名模組介面檔案 ().ifc的名稱。 例如:/ifcOutput "somethingelse.ifc"。 根據預設,編譯器會使用模組介面檔案 (.ifc) 與您正在編譯的模組原始檔 (.ixx) 相同的名稱。 在範例中,產生ifc的檔案預設為std.ifc,因為我們正在編譯模組檔案std.ixx。
-
匯入您建置的
std程式庫,方法是先建立具有下列內容的檔案importExample.cpp:// requires /std:c++latest import std; int main() { std::cout << "Import the STL library for best performance\n"; std::vector<int> v{5, 5, 5}; for (const auto& e : v) { std::cout << e; } }在上述程式碼中,
import std;會取代#include <vector>和#include <iostream>。 此陳述式import std;會讓所有標準程式庫都可用一個陳述式。 匯入整個標準程式庫通常比處理單一標準程式庫標頭檔(例如#include <vector>)快得多。在與上一個步驟相同的目錄中使用下列命令來編譯範例:
cl /c /std:c++latest /EHsc /nologo /W4 /reference "std=std.ifc" importExample.cpp link importExample.obj std.obj在此範例中,不需要在命令列上指定
/reference "std=std.ifc",因為編譯器會自動尋找.ifc符合陳述式所import指定模組名稱的檔案。 當編譯器遇到import std;時,它可以找到std.ifc它是否與原始碼位於相同的目錄中。 如果檔案.ifc位於與原始碼不同的目錄中,請使用/reference編譯器開關來參考它。在此範例中,編譯原始程式碼和將模組的實作連結到應用程式中是單獨的步驟。 他們不必如此。 您可以使用
cl /std:c++latest /EHsc /nologo /W4 /reference "std=std.ifc" importExample.cpp std.obj在同一個步驟中完成編譯和鏈接。 但單獨建置和連結可能會很方便,因為這樣你只需要建置一次名為 module 的標準函式庫,然後你就可以在建置的連結步驟中從你的專案或多個專案中引用它。如果您要建置單一專案,您可以結合建置
std名為 module 的標準程式庫的步驟,以及透過新增至"%VCToolsInstallDir%\modules\std.ixx"命令列來建置應用程式的步驟。 將它放在任何.cpp使用std模組的檔案之前。依預設,輸出可執行檔的名稱取自第一個輸入檔案。 使用
/Fe編譯器選項來指定您想要的可執行檔名。 本教學課程示範將std具名模組編譯為一個獨立的步驟,因為您只需建置一次具名模組的標準程式庫,然後就可以從專案或多個專案中引用它。 但一起建置所有內容可能會很方便,如以下命令列所示:cl /FeimportExample /std:c++latest /EHsc /nologo /W4 "%VCToolsInstallDir%\modules\std.ixx" importExample.cpp給定前一個命令行,編譯器會產生一個名為
importExample.exe的可執行檔。 當您執行它時,它會產生下列輸出:Import the STL library for best performance 555
匯入標準函式庫和全域 C 函數 std.compat
C++ 標準程式庫包含 ISO C 標準程式庫。 該std.compat模塊提供模塊std的所有功能,例如 std::vector、 、 std::coutstd::printfstd::scanf、 等。 但它也提供了這些函數的全域命名空間版本,例如 ::printf、 、 ::scanf::fopen::size_t、 等。
std.compat具名模組是提供的相容性層,可輕鬆移轉參考全域命名空間中 C 執行階段函式的現有程式碼。 如果您想避免將名稱新增至全域命名空間,請使用 import std;。 如果您需要輕鬆移轉使用許多非限定 (全域命名空間) C 執行階段函式的程式碼庫,請使用 import std.compat;。 這會提供全域命名空間 C 執行階段名稱,因此您不必使用 std::來限定所有全域名稱。 如果您沒有任何使用全域命名空間 C 執行階段函數的現有程式碼,則不需要使用 import std.compat;。 如果您在程式碼中只呼叫幾個 C 執行階段函數,則最好使用 import std; 並對於少數需要的全域命名空間 C 執行階段名稱使用 std:: 來限定它們。 例如: std::printf() 。 如果您在嘗試編譯程式碼時看到類似錯誤 error C3861: 'printf': identifier not found,請考慮使用 import std.compat; 來匯入全域命名空間的 C 執行階段函式。
範例:如何建置和匯入 std.compat
在您可以使用 import std.compat; 之前,您必須編譯在原始碼格式 std.compat.ixx中找到的模組介面檔案。 Visual Studio 會隨附模組的原始程式碼,讓您可以使用符合專案的編譯器設定來編譯模組。 這些步驟類似於建置 std 具名模組。 首先構建命名 std 模塊,因為 std.compat 依賴它:
開啟 VS 的本機工具命令提示字元:從 Windows 的「開始」 功能表中,鍵入 x86 native ,提示應該會出現在應用程式清單中。 請確定提示適用於 Visual Studio 2022 17.5 版或更新版本。 如果您使用錯誤版本的提示,您將收到編譯器錯誤。
建立一個目錄來嘗試這個範例,例如
%USERPROFILE%\source\repos\STLModules,並使其成為目前的目錄。 如果您選擇沒有寫入權限的目錄,則會收到錯誤。使用下列指令編譯
std和std.compat具名模組:cl /std:c++latest /EHsc /nologo /W4 /c "%VCToolsInstallDir%\modules\std.ixx" "%VCToolsInstallDir%\modules\std.compat.ixx"您應該編譯
std並std.compat使用您想要與匯入它們的程式碼搭配使用的相同編譯器設定。 如果您有多專案方案,您可以編譯它們一次,然後使用/reference編譯器選項來引用所有專案中的它們。如果您收到錯誤,請確定您使用的是正確版本的命令提示字元。
編譯器會從前兩個步驟輸出四個檔案:
-
std.ifc是編譯器查閱以處理import std;陳述式的已編譯二進位命名模組介面。 編譯器也會諮詢std.ifc以處理import std.compat;,因為std.compat建置在std之上。 這是僅限編譯時間的成品。 它不隨附您的應用程式。 -
std.obj包含標準程式庫的實作。 -
std.compat.ifc是編譯器查閱以處理import std.compat;陳述式的已編譯二進位命名模組介面。 這是僅在編譯時使用的產物。 它不隨附您的應用程式。 -
std.compat.obj包含實作。 不過,大部分的實作是由std.obj提供。 當您編譯範例應用程式時,新增至std.obj命令列,以靜態方式將您從標準程式庫使用的功能連結至應用程式。
您可以使用下列參數來控制物件檔名及具名模組介面檔名:
-
/Fo設定物件檔的名稱。 例如:/Fo:"somethingelse"。 依預設,編譯器會使用與您正在編譯的模組原始檔 (.ixx) 相同的目標檔名稱。 在此範例中,物件檔名預設為std.obj和std.compat.obj,因為我們正在編譯模組檔案std.ixx和std.compat.ixx。 -
/ifcOutput設定具名模組介面檔案 ().ifc的名稱。 例如:/ifcOutput "somethingelse.ifc"。 根據預設,編譯器會使用模組介面檔案 (.ifc) 與您正在編譯的模組原始檔 (.ixx) 相同的名稱。 在此範例中,產生ifc的檔案預設為std.ifc和std.compat.ifc,因為我們正在編譯模組檔案std.ixx和std.compat.ixx。
-
先建立以下列內容命名的檔案
std.compat來匯入stdCompatExample.cpp程式庫:import std.compat; int main() { printf("Import std.compat to get global names like printf()\n"); std::vector<int> v{5, 5, 5}; for (const auto& e : v) { printf("%i", e); } }在上述程式碼中,
import std.compat;會取代#include <cstdio>和#include <vector>。 此陳述式import std.compat;可讓標準程式庫及 C 執行時期函數與一個陳述式搭配使用。 匯入這個命名模組(包含 C++ 標準函式庫和 C 執行階段函式庫的全域命名空間函式)比處理單一#include如同#include <vector>更快。使用下列命令編譯範例:
cl /std:c++latest /EHsc /nologo /W4 stdCompatExample.cpp link stdCompatExample.obj std.obj std.compat.obj我們不必在命令列上指定
std.compat.ifc,因為編譯器會自動尋找.ifc與語句中import模組名稱相符的檔案。 當編譯器遇到import std.compat;它時,它會發現std.compat.ifc,因為我們將它放在與原始程式碼相同的目錄中,從而使我們無需在命令列上指定它。 如果檔案.ifc位於與原始碼不同的目錄中,或具有不同的名稱,請使用/reference編譯器參數來參照它。當您匯入
std.compat時,您必須同時連結std.compat和std.obj,因為std.compat使用std.obj中的程式碼。如果您要建置單一專案,您可以將
"%VCToolsInstallDir%\modules\std.ixx"和"%VCToolsInstallDir%\modules\std.compat.ixx"(依序)新增至命令列,以結合建置std和std.compat標準程式庫命名模組的步驟。 本教學課程示範建置標準程式庫模組作為個別步驟,因為您只需要建置名為 modules 的標準程式庫一次,然後就可以從專案或多個專案參考它們。 但是,如果一次建構它們很方便,請確保將它們放在任何.cpp使用它們的檔案之前,並指定使用/Fe來命名已建構的exe,如以下範例所示:cl /c /FestdCompatExample /std:c++latest /EHsc /nologo /W4 "%VCToolsInstallDir%\modules\std.ixx" "%VCToolsInstallDir%\modules\std.compat.ixx" stdCompatExample.cpp link stdCompatExample.obj std.obj std.compat.obj在此範例中,編譯原始程式碼和將模組的實作連結到應用程式是單獨的步驟。 他們不必如此。 您可以使用
cl /std:c++latest /EHsc /nologo /W4 stdCompatExample.cpp std.obj std.compat.obj在同一個步驟中完成編譯和鏈接。 但單獨建置和連結可能會很方便,因為這樣您只需要建置一次名為模組的標準庫,然後您就可以在建置的連結步驟中從專案或多個專案中引用它們。前一個編譯器命令會產生名為
stdCompatExample.exe的可執行檔。 當您執行它時,它會產生下列輸出:Import std.compat to get global names like printf() 555
標準函式庫命名模組考量
具名模組的版本設定與標頭相同。
.ixx具名模組檔案會與標頭一起安裝,例如:"%VCToolsInstallDir%\modules\std.ixx"在撰寫本文時使用的工具版本中解析為C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.38.33130\modules\std.ixx。 選擇具名模組的版本,就如同選擇要使用的標頭檔版本一樣,透過您引用它們所在的目錄進行選擇。
請勿混合搭配匯入標頭單位和具名模組。 例如,不要 import <vector>; 和 import std; 在同一個檔案中。
請勿混合搭配匯入 C++ 標準程式庫標頭檔與具名模組 std 或 std.compat。 例如,不要 #include <vector> 和 import std; 在同一個檔案中。 不過,您可以包含 C 標頭,並在相同的檔案中匯入具名模組。 例如,您可以在同一個檔案中import std;和#include <math.h>。 只是不要包含 C++ 標準函式庫版本 <cmath>。
您不必多次導入模塊。 也就是說,您不需要 #ifndef 模組中的樣式標頭防護。 編譯器知道它何時已經匯入具名模組,並忽略重複的嘗試。
如果需要使用 assert() 巨集,則 #include <assert.h>。
如果您需要使用 errno 巨集, #include <errno.h>. 因為具名模組不會公開巨集,因此您需要檢查來自<math.h>的錯誤時,這是因應措施。
巨集 (例如 NAN、 INFINITY和 INT_MIN ) 是由 定義的 <limits.h>,您可以併入這些巨集。 但是,如果您 import std;,可以使用 std::numeric_limits<double>::quiet_NaN() 和 std::numeric_limits<double>::infinity() 代替 NAN 和 INFINITY,並且使用 std::numeric_limits<int>::min() 代替 INT_MIN。
總結
在本教學課程中,您已使用模組匯入標準程式庫。 接下來,在 C++ 的具名模組教學課程中瞭解如何建立和匯入您自己的模組。
另請參閱
比較標頭單元、模組和預先編譯的標頭
C++ 中的模組概述
Visual Studio 中的 C++ 模組導覽
將專案移至名為 Modules 的 C++