教學課程:從命令列使用模組匯入 C++ 標準程式庫
了解如何使用 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;
或 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
檔案快得多,而且在編譯時間也使用較少的記憶體。 具名模組不會公開巨集定義或私人實作詳細資料。
如需關於模組的深入資訊,請參閱 C++ 中的模組概觀。本文也會討論使用 C++ 標準程式庫作為模組,但會使用較舊且實驗性的方式來執行。
本文示範取用標準程式庫的新方式和最佳方式。 如需取用標準程式庫替代方式的詳細資訊,請參閱比較標頭單位、模組和先行編譯標頭檔。
使用 std
匯入標準程式庫
下列範例示範如何取用命令列編譯器,以模組的形式取用標準程式庫。 如需如何在Visual Studio IDE 中執行這項操作的詳細資訊,請參閱建置 ISO C++23 標準程式庫模組。
陳述式 import std;
或 import std.compat;
會將標準程式庫匯入至您的應用程式。 但首先,您必須將標準程式庫具名模組編譯為二進位格式。 下列步驟示範如何執行。
範例:如何建置和匯入 std
開啟適用於 VS 的 x86 Native Tools 命令提示字元:從 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
具名模組。 如果您有多專案方案,則可以編譯標準程式庫具名模組一次,然後使用/reference
編譯器選項從所有專案參考它。使用先前的編譯器命令,編譯器會輸出兩個檔案:
std.ifc
是編譯器參考以處理import std;
陳述式之具名模組介面的已編譯二進位表示法。 這是僅限編譯時間的成品。 其不會隨附於您的應用程式。std.obj
包含具名模組的實作。 當您編譯範例應用程式並以靜態方式將您使用的功能從標準程式庫連結至應用程式時,請新增std.obj
至命令列。
此範例中的主要命令列參數如下:
交換器 意義 /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"
,因為編譯器會自動尋找符合import
陳述式所指定模組名稱的.ifc
檔案。 當編譯器遇到import std;
時,其可以找到std.ifc
(若位於與原始程式碼相同的目錄中)。 如果.ifc
檔案位於與原始程式碼不同的目錄中,請使用/reference
編譯器參數來參考它。在此範例中,編譯原始程式碼並將模組的實作連結至應用程式是個別的步驟。 這些不是必要步驟。 您可以使用
cl /std:c++latest /EHsc /nologo /W4 /reference "std=std.ifc" importExample.cpp std.obj
在單一步驟中進行編譯和連結。 但是,個別建置和連結可能很方便,因為您只需要在編譯的連結步驟中建立標準程式庫具名模組一次,然後您就可以從專案或多個專案參考它。如果您要建置單一專案,則可以結合建置
std
標準程式庫具名模組的步驟,以及藉由將"%VCToolsInstallDir%\modules\std.ixx"
新增至命令列來建置應用程式的步驟。 將其放置在取用std
模組的任何.cpp
檔案之前。根據預設,輸出可執行檔的名稱取自第一個輸入檔。 使用
/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
使用 std.compat
匯入標準程式庫和全域 C 函式
C++ 標準程式庫包含 ISO C 標準程式庫。 std.compat
模組提供 std
模組的所有功能,例如 std::vector
、std::cout
、std::printf
、std::scanf
等等。 但其也會提供這些函式的全域命名空間版本,例如 ::printf
、::scanf
、::fopen
、::size_t
等等。
std.compat
具名模組是一個相容性層,可簡化移轉參考全域命名空間中 C 執行階段函式的現有程式碼。 如果您要避免將名稱新增至全域命名空間,請使用 import std;
。 如果您需要輕鬆移轉使用許多不合格 (全域命名空間) C 執行階段函式的程式碼基底,請使用 import std.compat;
。 這會提供全域命名空間 C 執行階段名稱,因此您不需要使用 std::
來限定所有全域名稱。 如果您沒有任何使用全域命名空間 C 執行階段函式的現有程式碼,則不需要使用 import std.compat;
。 如果您只在程式碼中呼叫幾個 C 執行階段函式,最好是使用 import std;
,並使用 std::
來限定幾個需要它的全域命名空間 C 執行階段名稱。 例如: 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 的 Native Tools 命令提示字元:從 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.obj
。/ifcOutput
會設定具名模組介面檔案 (.ifc
) 的名稱。 例如:/ifcOutput "somethingelse.ifc"
。 根據預設,編譯器會針對模組介面檔案 (.ifc
) 使用與您正在編譯之模組來源檔案 (.ixx
) 相同的名稱。 在此範例中,產生的ifc
檔案預設為std.ifc
和std.compat.ifc
,因為我們正在編譯模組檔案std.ixx
和std.compat.ixx
。
先建立名為
stdCompatExample.cpp
且包含下列內容的檔案,以匯入std.compat
程式庫: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 <vector>
的單一#include
更快。使用下列命令編譯範例:
cl /std:c++latest /EHsc /nologo /W4 stdCompatExample.cpp link stdCompatExample.obj std.obj std.compat.obj
我們不需要在命令列上指定
std.compat.ifc
,因為編譯器會自動尋找符合import
陳述式中模組名稱的.ifc
檔案。 當編譯器遇到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
標準程式庫具名模組的步驟。 本教學課程示範將標準程式庫模組建置為個別步驟,因為您只需要建置一次標準程式庫具名模組,然後您就可以從專案或多個專案參考它們。 但是,如果一次建置很方便,請務必將其放置在取用它們的任何.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;
,則可以使用 numeric_limits<double>::quiet_NaN()
和 numeric_limits<double>::infinity()
,而不是 NAN
和 INFINITY
,以及使用 std::numeric_limits<int>::min()
而不是 INT_MIN
。
摘要
在本教學課程中,您已使用模組匯入標準程式庫。 接下來,了解如何在 C++ 中的具名模組教學課程中建立和匯入您自己的模組。
另請參閱
比較標頭單位、模組和先行編譯標頭檔
C++ 中的模組概觀
Visual Studio 中的 C++ 模組導覽
將專案移至 C++ 具名模組
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應