共用方式為


教學課程:從命令列使用模組匯入 C++ 標準程式庫

了解如何使用 C++ 程式庫模組匯入 C++ 標準程式庫。 這會產生更快的編譯速度,而且比使用標頭檔或標頭單位或先行編譯標頭檔 (PCH) 更為強固。

在本教學課程中,了解:

  • 如何從命令列將標準程式庫匯入為模組。
  • 模組的效能和可用性優點。
  • 兩個標準程式庫模組 stdstd.compat 以及兩者之間的差異。

必要條件

本教學課程需要 Visual Studio 2022 17.5 或更新版本。

標準程式庫模組簡介

標頭檔會受到語意的影響,這些語意可能會根據巨集定義、納入的順序,及編譯速度緩慢而變更。 模組可解決這些問題。

現在可以將標準程式庫匯入為模組,而不是雜亂的標頭檔。 這比包括標頭檔或標頭單位或先行編譯標頭檔 (PCH) 更快且更強固。

C++23 標準程式庫引進兩個具名模組:stdstd.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> 來得更快。

由於具名模組不會公開巨集,因此當您匯入 stdstd.compat 時,無法使用 asserterrnooffsetofva_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

  1. 開啟適用於 VS 的 x86 Native Tools 命令提示字元:從 Windows [開始] 功能表,輸入 x86 native,提示應該會出現在應用程式清單中。 確定提示是針對 Visual Studio 2022 17.5 版或更新版本。 如果您使用錯誤的提示版本,您將會收到錯誤。 本教學課程中使用的範例適用於 CMD 殼層。

  2. 建立目錄,例如 %USERPROFILE%\source\repos\STLModules,並將其設為目前目錄。 如果您選擇沒有寫入存取權的目錄,則在編譯期間會收到錯誤。

  3. 使用下列命令編譯 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
  4. 匯入您建立的 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>

  5. 在與上一個步驟相同的目錄中,使用下列命令來編譯範例:

    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::vectorstd::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;,並使用 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 取決於它:

  1. 開啟適用於 VS 的 Native Tools 命令提示字元:從 Windows [開始] 功能表,輸入 x86 native,提示應該會出現在應用程式清單中。 確定提示是針對 Visual Studio 2022 17.5 版或更新版本。 如果您使用錯誤的提示版本,您將會收到編譯器錯誤。

  2. 建立目錄以嘗試此範例,例如 %USERPROFILE%\source\repos\STLModules,並將其設為目前的目錄。 如果您選擇沒有寫入存取權的目錄,則將收到錯誤。

  3. 使用下列命令編譯 stdstd.compat 具名模組:

    cl /std:c++latest /EHsc /nologo /W4 /c "%VCToolsInstallDir%\modules\std.ixx" "%VCToolsInstallDir%\modules\std.compat.ixx"
    

    您應該使用您想要在匯入程式碼時搭配使用的相同編譯器設定來編譯 stdstd.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.objstd.compat.obj,因為我們正在編譯模組檔案 std.ixxstd.compat.obj
    • /ifcOutput 會設定具名模組介面檔案 (.ifc) 的名稱。 例如: /ifcOutput "somethingelse.ifc" 。 根據預設,編譯器會針對模組介面檔案 (.ifc) 使用與您正在編譯之模組來源檔案 (.ixx) 相同的名稱。 在此範例中,產生的 ifc 檔案預設為 std.ifcstd.compat.ifc,因為我們正在編譯模組檔案 std.ixxstd.compat.ixx
  4. 先建立名為 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 更快。

  5. 使用下列命令編譯範例:

    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.compatstd.obj,因為 std.compat 會在 std.obj 中使用程式碼。

    如果您要建置單一專案,則可以將 "%VCToolsInstallDir%\modules\std.ixx""%VCToolsInstallDir%\modules\std.compat.ixx" (依該順序) 新增至命令列,以結合建置 stdstd.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++ 標準程式庫標頭檔與具名模組 stdstd.compat。 例如,不要在相同檔案中使用 #include <vector>import std;。 不過,您可以在相同的檔案中包含 C 標頭並匯入具名模組。 例如,您可以在相同檔案中使用 import std;#include <math.h>。 只是不要包含 C++ 標準連結函式庫版本 <cmath>

您不必防範多次匯入模組。 也就是說,您不需要模組中的 #ifndef 樣式標頭防護。 編譯器知道它何時已經匯入具名模組,並忽略重複這樣做的嘗試。

如果您需要使用 assert() 巨集,則為 #include <assert.h>

如果您需要使用 errno 巨集,則為 #include <errno.h>。 因為具名模組不會公開巨集,因此舉例來說,如果您需要檢查來自 <math.h> 的錯誤,這便是因應措施。

NANINFINITY、和 INT_MIN 等巨集是由 <limits.h> 所定義,您可以包含該巨集。 不過,如果您使用 import std;,則可以使用 numeric_limits<double>::quiet_NaN()numeric_limits<double>::infinity(),而不是 NANINFINITY,以及使用 std::numeric_limits<int>::min() 而不是 INT_MIN

摘要

在本教學課程中,您已使用模組匯入標準程式庫。 接下來,了解如何在 C++ 中的具名模組教學課程中建立和匯入您自己的模組。

另請參閱

比較標頭單位、模組和先行編譯標頭檔
C++ 中的模組概觀
Visual Studio 中的 C++ 模組導覽
將專案移至 C++ 具名模組