Udostępnij za pośrednictwem


Samouczek dotyczący nazwanych modułów (C++)

Ten samouczek dotyczy tworzenia modułów języka C++20. Moduły zastępują pliki nagłówków. Dowiesz się, jak moduły są ulepszeniem plików nagłówkowych.

Z tego samouczka dowiesz się, jak wykonywać następujące czynności:

  • Tworzenie i importowanie modułu
  • Tworzenie jednostki interfejsu modułu podstawowego
  • Tworzenie pliku partycji modułu
  • Tworzenie pliku implementacji jednostki modułu

Wymagania wstępne

Ten samouczek wymaga programu Visual Studio 2022 w wersji 17.1.0 lub nowszej.

Błędy funkcji IntelliSense mogą wystąpić podczas pracy z przykładem kodu w tym samouczku. Praca nad aparatem IntelliSense nadrabia zaległości w kompilatorze. Błędy funkcji IntelliSense można zignorować i nie uniemożliwią kompilowania przykładu kodu. Aby śledzić postęp pracy funkcji IntelliSense, zobacz ten problem.

Co to są moduły języka C++

Pliki nagłówkowe to sposób udostępniania deklaracji i definicji między plikami źródłowymi w języku C++. Pliki nagłówkowe są kruche i trudne do skomponowania. Mogą one być kompilowane inaczej w zależności od kolejności ich dołączenia lub makr, które są lub nie są zdefiniowane. Mogą spowalniać czas kompilacji, ponieważ są one ponownie przetwarzane dla każdego pliku źródłowego, który je zawiera.

Język C++20 wprowadza nowoczesne podejście do składnikyzacji programów C++: modules.

Podobnie jak pliki nagłówkowe, moduły umożliwiają udostępnianie deklaracji i definicji między plikami źródłowymi. Jednak w przeciwieństwie do plików nagłówkowych moduły nie wyciekają definicji makr ani prywatnych szczegółów implementacji.

Moduły są łatwiejsze do redagowania, ponieważ ich semantyka nie zmienia się z powodu definicji makr lub importowanych elementów, kolejności importu itd. Ułatwiają one również kontrolowanie tego, co jest widoczne dla konsumentów.

Moduły zapewniają dodatkowe gwarancje bezpieczeństwa, że pliki nagłówków nie. Kompilator i konsolidator współpracują ze sobą, aby zapobiec ewentualnym problemom z kolizją nazw i zapewnić silniejszą jedną regułę definicji (ODR).

Model silnej własności pozwala uniknąć starć między nazwami w czasie połączenia, ponieważ konsolidator dołącza wyeksportowane nazwy do modułu, który je eksportuje. Ten model umożliwia kompilatorowi programu Microsoft Visual C++ zapobieganie niezdefiniowanym zachowaniom spowodowanym łączeniem różnych modułów, które zgłaszają podobne nazwy w tym samym programie. Aby uzyskać więcej informacji, zobacz Silna własność.

Moduł składa się z co najmniej jednego pliku kodu źródłowego skompilowanego w pliku binarnym. Plik binarny opisuje wszystkie wyeksportowane typy, funkcje i szablony w module. Gdy plik źródłowy importuje moduł, kompilator odczytuje w pliku binarnym zawierającym zawartość modułu. Odczytywanie pliku binarnego jest znacznie szybsze niż przetwarzanie pliku nagłówka. Ponadto plik binarny jest ponownie używany przez kompilator za każdym razem, gdy moduł jest importowany, co pozwala zaoszczędzić jeszcze więcej czasu. Ponieważ moduł jest kompilowany raz, a nie za każdym razem, gdy jest importowany, czas kompilacji można skrócić, czasami dramatycznie.

Co ważniejsze, moduły nie mają problemów z kruchością, które wykonują pliki nagłówka. Importowanie modułu nie zmienia semantyki modułu ani semantyki żadnego innego zaimportowanego modułu. Makra, dyrektywy preprocesora i nazwy nie wyeksportowane zadeklarowane w module nie są widoczne dla pliku źródłowego, który go importuje. Moduły można zaimportować w dowolnej kolejności i nie zmienią znaczenia modułów.

Moduły mogą być używane obok plików nagłówków. Ta funkcja jest wygodna, jeśli migrujesz bazę kodu do używania modułów, ponieważ możesz to zrobić na etapach.

W niektórych przypadkach plik nagłówka można zaimportować jako jednostkę nagłówka, a nie jako #include plik. Jednostki nagłówka są zalecaną alternatywą dla wstępnie skompilowanych plików nagłówków (PCH). Są one łatwiejsze do skonfigurowania i użycia niż udostępnione pliki PCH , ale zapewniają podobne korzyści z wydajności. Aby uzyskać więcej informacji, zobacz Przewodnik: kompilowanie i importowanie jednostek nagłówków w programie Microsoft Visual C++.

Kod może używać modułów w tym samym projekcie lub dowolnych projektach, do których się odwołujesz, automatycznie przy użyciu odwołań projektu do projektu biblioteki statycznej.

Tworzenie projektu

Podczas tworzenia prostego projektu przyjrzymy się różnym aspektom modułów. Projekt zaimplementuje interfejs API przy użyciu modułu zamiast pliku nagłówka.

W programie Visual Studio 2022 lub nowszym wybierz pozycję Utwórz nowy projekt, a następnie typ projektu Aplikacja konsolowa (dla języka C++). Jeśli ten typ projektu nie jest dostępny, być może nie wybrano obciążenia Programowanie aplikacji klasycznych w języku C++ podczas instalowania programu Visual Studio. Aby dodać obciążenie języka C++, możesz użyć Instalator programu Visual Studio.

Nadaj nowemu projektowi nazwę ModulesTutorial i utwórz projekt.

Ponieważ moduły są funkcją języka C++20, użyj /std:c++20 opcji lub /std:c++latest kompilatora. W Eksplorator rozwiązań kliknij prawym przyciskiem myszy nazwę ModulesTutorialprojektu, a następnie wybierz polecenie Właściwości. W oknie dialogowym Strony właściwości projektu zmień pozycję Konfiguracja na Wszystkie konfiguracje i Platforma na Wszystkie platformy. Wybierz pozycję Właściwości>konfiguracji Ogólne w okienku widoku drzewa po lewej stronie. Wybierz właściwość C++ Language Standard. Użyj listy rozwijanej, aby zmienić wartość właściwości na ISO C++20 Standard (/std:c++20). Wybierz przycisk OK , aby zaakceptować zmianę.

A screenshot of the ModulesTutorial property page with the left pane open to Configuration Properties > General, and the C++ Language Standard dropdown open with ISO C++20 Standard (/std:c++20) selected

Tworzenie jednostki interfejsu modułu podstawowego

Moduł składa się z co najmniej jednego pliku. Jednym z tych plików musi być to, co jest nazywane podstawową jednostką interfejsu modułu. Definiuje to, co eksportuje moduł; oznacza to, co zobaczą importerzy modułu. Dla każdego modułu może istnieć tylko jedna jednostka interfejsu podstawowego modułu.

Aby dodać jednostkę interfejsu modułu podstawowego, w Eksplorator rozwiązań kliknij prawym przyciskiem myszy pozycję Pliki źródłowe, a następnie wybierz polecenie Dodaj>moduł.

Add item dialog in solution explorer with Add > Module... highlighted to illustrate where to click to add a module.

W wyświetlonym oknie dialogowym Dodawanie nowego elementu nadaj nowemu modułowi nazwę BasicPlane.Figures.ixx i wybierz pozycję Dodaj.

Domyślna zawartość utworzonego pliku modułu zawiera dwa wiersze:

export module BasicPlane;

export void MyFunc();

Słowa export module kluczowe w pierwszym wierszu deklarują, że ten plik jest jednostką interfejsu modułu. W tym miejscu istnieje subtelny punkt: dla każdego nazwanego modułu musi istnieć dokładnie jedna jednostka interfejsu modułu bez określonej partycji modułu. Ta jednostka modułu jest nazywana podstawową jednostką interfejsu modułu.

Podstawowa jednostka interfejsu modułu to miejsce, w którym deklarujesz funkcje, typy, szablony, inne moduły i partycje modułów w celu uwidocznienia, gdy pliki źródłowe zaimportują moduł. Moduł może składać się z wielu plików, ale tylko podstawowy plik interfejsu modułu identyfikuje, co należy uwidocznić.

Zastąp zawartość BasicPlane.Figures.ixx pliku:

export module BasicPlane.Figures; // the export module keywords mark this file as a primary module interface unit

Ten wiersz identyfikuje ten plik jako interfejs modułu podstawowego i nadaje modułowi nazwę: BasicPlane.Figures. Kropka w nazwie modułu nie ma specjalnego znaczenia dla kompilatora. Okres może służyć do przekazywania sposobu organizowania modułu. Jeśli masz wiele plików modułów, które współpracują ze sobą, możesz użyć okresów, aby wskazać separację problemów. W tym samouczku użyjemy okresów, aby wskazać różne obszary funkcjonalne interfejsu API.

Ta nazwa jest również miejscem, z którego pochodzi "nazwany" w "nazwanym module". Pliki, które są częścią tego modułu, używają tej nazwy do identyfikowania się jako część nazwanego modułu. Nazwany moduł to kolekcja jednostek modułu o tej samej nazwie modułu.

Zanim przejdziemy dalej, powinniśmy porozmawiać o interfejsie API, który wdrożymy na chwilę. Ma to wpływ na wybory, które dokonujemy dalej. Interfejs API reprezentuje różne kształty. W tym przykładzie udostępnimy tylko kilka kształtów: Point i Rectangle. Point ma być używany jako część bardziej złożonych kształtów, takich jak Rectangle.

Aby zilustrować niektóre funkcje modułów, uwzględnimy ten interfejs API w fragmenty. Jeden element będzie interfejsem Point API. Drugą częścią będzie Rectangle. Wyobraź sobie, że ten interfejs API będzie się rozwijać w coś bardziej złożonego. Podział jest przydatny do oddzielania problemów lub ułatwiania konserwacji kodu.

Do tej pory utworzyliśmy interfejs modułu podstawowego, który uwidoczni ten interfejs API. Skompilujmy Point teraz interfejs API. Chcemy, aby był on częścią tego modułu. Ze względu na logiczną organizację i potencjalną wydajność kompilacji chcemy uczynić tę część interfejsu API łatwo zrozumiałym samodzielnie. W tym celu utworzymy plik partycji modułu.

Plik partycji modułu jest elementem lub składnikiem modułu. To, co sprawia, że jest unikatowe, może być traktowane jako pojedynczy element modułu, ale tylko w ramach modułu. Partycji modułu nie można używać poza modułem. Partycje modułów są przydatne do dzielenia implementacji modułu na elementy, którymi można zarządzać.

Podczas importowania partycji do modułu podstawowego wszystkie jego deklaracje stają się widoczne dla modułu podstawowego niezależnie od tego, czy są eksportowane. Partycje można zaimportować do dowolnego interfejsu partycji, podstawowego interfejsu modułu lub jednostki modułu należącej do nazwanego modułu.

Tworzenie pliku partycji modułu

Point partycja modułu

Aby utworzyć plik partycji modułu, w Eksplorator rozwiązań kliknij prawym przyciskiem myszy pozycję Pliki źródłowe, a następnie wybierz polecenie Dodaj>moduł. Nadaj plikowi BasicPlane.Figures-Point.ixx nazwę i wybierz pozycję Dodaj.

Ponieważ jest to plik partycji modułu, dodaliśmy łącznik i nazwę partycji do nazwy modułu. Ta konwencja ułatwia kompilatorowi w przypadku wiersza polecenia, ponieważ kompilator używa reguł wyszukiwania nazw na podstawie nazwy modułu w celu znalezienia skompilowanego .ifc pliku partycji. W ten sposób nie trzeba podawać jawnych /reference argumentów wiersza polecenia w celu znalezienia partycji należących do modułu. Przydatne jest również organizowanie plików należących do modułu według nazwy, ponieważ można łatwo zobaczyć, które pliki należą do których modułów.

Zastąp zawartość elementu BasicPlane.Figures-Point.ixx następującym kodem:

export module BasicPlane.Figures:Point; // defines a module partition, Point, that's part of the module BasicPlane.Figures

export struct Point
{
    int x, y;
};

Plik rozpoczyna się od export module. Te słowa kluczowe są również sposobem rozpoczęcia interfejsu modułu podstawowego. To, co sprawia, że ten plik różni się, to dwukropek (:) po nazwie modułu, po którym następuje nazwa partycji. Ta konwencja nazewnictwa identyfikuje plik jako partycję modułu. Ponieważ definiuje interfejs modułu dla partycji, nie jest uważany za interfejs modułu podstawowego.

Nazwa BasicPlane.Figures:Point identyfikuje tę partycję w ramach modułu BasicPlane.Figures. (Pamiętaj, że kropka w nazwie nie ma specjalnego znaczenia dla kompilatora). Dwukropek wskazuje, że ten plik zawiera partycję modułu o nazwie Point , która należy do modułu BasicPlane.Figures. Tę partycję można zaimportować do innych plików, które są częścią tego nazwanego modułu.

W tym pliku export słowo kluczowe jest struct Point widoczne dla użytkowników.

Rectangle partycja modułu

Następna partycja, która zdefiniujemy, to Rectangle. Utwórz inny plik modułu, wykonując te same kroki co poprzednio: w Eksplorator rozwiązań kliknij prawym przyciskiem myszy pozycję Pliki źródłowe, a następnie wybierz polecenie Dodaj>moduł. Nadaj plikowi BasicPlane.Figures-Rectangle.ixx nazwę i wybierz pozycję Dodaj.

Zastąp zawartość elementu BasicPlane.Figures-Rectangle.ixx następującym kodem:

export module BasicPlane.Figures:Rectangle; // defines the module partition Rectangle

import :Point;

export struct Rectangle // make this struct visible to importers
{
    Point ul, lr;
};

// These functions are declared, but will
// be defined in a module implementation file
export int area(const Rectangle& r);
export int height(const Rectangle& r);
export int width(const Rectangle& r);

Plik rozpoczyna się od, z export module BasicPlane.Figures:Rectangle; którym deklaruje partycję modułu, która jest częścią modułu BasicPlane.Figures. Dodany :Rectangle do nazwy modułu definiuje go jako partycję modułu BasicPlane.Figures. Można go zaimportować indywidualnie do dowolnego z plików modułu, które są częścią tego nazwanego modułu.

Następnie pokazano, import :Point; jak zaimportować partycję modułu. Instrukcja import sprawia, że wszystkie wyeksportowane typy, funkcje i szablony w partycji modułu są widoczne dla modułu. Nie musisz określać nazwy modułu. Kompilator wie, że ten plik należy do BasicPlane.Figures modułu z powodu export module BasicPlane.Figures:Rectangle; pliku w górnej części pliku.

Następnie kod eksportuje definicję struct Rectangle i deklaracje dla niektórych funkcji, które zwracają różne właściwości prostokąta. Słowo export kluczowe wskazuje, czy element ma być widoczny dla użytkowników modułu. Służy do tworzenia funkcji area, heighti width widocznych poza modułem.

Wszystkie definicje i deklaracje w partycji modułu są widoczne dla jednostki modułu importowania, niezależnie od tego export , czy mają słowo kluczowe, czy nie. Słowo export kluczowe określa, czy definicja, deklaracja lub definicja typu będą widoczne poza modułem podczas eksportowania partycji w interfejsie modułu podstawowego.

Nazwy są widoczne dla użytkowników modułu na kilka sposobów:

  • Umieść słowo kluczowe przed każdym typem export , funkcją itd., które chcesz wyeksportować.
  • Jeśli umieścisz export przed przestrzenią nazw, na przykład export namespace N { ... }, wszystko zdefiniowane w nawiasach klamrowych zostanie wyeksportowane. Jeśli jednak w innym miejscu w zdefiniowanym module namespace N { struct S {...};}nie struct S jest dostępny dla użytkowników modułu. Nie jest dostępna, ponieważ ta deklaracja przestrzeni nazw nie jest poprzedzona exportznakiem , mimo że istnieje inna przestrzeń nazw o tej samej nazwie.
  • Jeśli typ, funkcja itd., nie należy eksportować, pomijać słowa kluczowego export . Będzie on widoczny dla innych plików, które są częścią modułu, ale nie dla importerów modułu.
  • Służy module :private; do oznaczania początku partycji modułu prywatnego. Partycja modułu prywatnego to sekcja modułu, w której deklaracje są widoczne tylko dla tego pliku. Nie są one widoczne dla plików, które importują ten moduł lub do innych plików, które są częścią tego modułu. Pomyśl o tym jako o sekcji, która jest statyczna lokalnie w pliku. Ta sekcja jest widoczna tylko w pliku.
  • Aby uwidocznić zaimportowany moduł lub partycję modułu, użyj polecenia export import. Przykład pokazano w następnej sekcji.

Tworzenie partycji modułu

Teraz, gdy mamy zdefiniowaną dwie części interfejsu API, połączmy je razem, aby pliki importowane w tym module mogły uzyskiwać dostęp do nich jako całości.

Wszystkie partycje modułu muszą być uwidocznione w ramach definicji modułu, do której należą. Partycje są widoczne w interfejsie modułu podstawowego. BasicPlane.Figures.ixx Otwórz plik, który definiuje interfejs modułu podstawowego. Zastąp jego zawartość:

export module BasicPlane.Figures; // keywords export module marks this as a primary module interface unit

export import :Point; // bring in the Point partition, and export it to consumers of this module
export import :Rectangle; // bring in the Rectangle partition, and export it to consumers of this module

Dwa wiersze, które zaczynają się od export import , są tutaj nowe. W połączeniu w ten sposób te dwa słowa kluczowe instruują kompilatora, aby zaimportował określony moduł i uwidocznił go użytkownikom tego modułu. W takim przypadku dwukropek (:) w nazwie modułu wskazuje, że importujemy partycję modułu.

Zaimportowane nazwy nie zawierają pełnej nazwy modułu. Na przykład partycja :Point została zadeklarowana jako export module BasicPlane.Figures:Point. Jednak w tym miejscu importujemy :Pointelement . Ponieważ znajdujemy się w podstawowym pliku interfejsu modułu dla modułu BasicPlane.Figures, nazwa modułu jest implikowane i jest określona tylko nazwa partycji.

Do tej pory zdefiniowaliśmy podstawowy interfejs modułu, który uwidacznia powierzchnię interfejsu API, którą chcemy udostępnić. Ale zadeklarowaliśmy tylko, nie zdefiniowano, area(), lub height()width(). Następnie utworzymy plik implementacji modułu.

Tworzenie pliku implementacji jednostki modułu

Pliki implementacji jednostki modułu nie kończą się .ixx rozszerzeniem — są to zwykłe .cpp pliki. Dodaj plik implementacji jednostki modułu, tworząc plik źródłowy za pomocą prawym przyciskiem myszy w Eksplorator rozwiązań w plikach źródłowych, wybierz pozycję Dodaj>nowy element, a następnie wybierz pozycję Plik C++ (cpp). Nadaj nowemu plikowi nazwę BasicPlane.Figures-Rectangle.cpp, a następnie wybierz pozycję Dodaj.

Konwencja nazewnictwa pliku implementacji partycji modułu jest zgodna z konwencją nazewnictwa partycji. Ma jednak .cpp rozszerzenie, ponieważ jest to plik implementacji.

Zastąp zawartość BasicPlane.Figures-Rectangle.cpp pliku:

module;

// global module fragment area. Put #include directives here 

module BasicPlane.Figures:Rectangle;

int area(const Rectangle& r) { return width(r) * height(r); }
int height(const Rectangle& r) { return r.ul.y - r.lr.y; }
int width(const Rectangle& r) { return r.lr.x - r.ul.x; }

Ten plik zaczyna się od module; tego, od którego przedstawiono specjalny obszar modułu nazywany fragmentem modułu globalnego. Poprzedza kod nazwanego modułu i służy do używania dyrektyw preprocesora, takich jak #include. Kod w części modułu globalnego nie jest własnością ani nie jest eksportowany przez interfejs modułu.

Po dołączeniu pliku nagłówka zazwyczaj nie chcesz, aby był traktowany jako wyeksportowana część modułu. Zazwyczaj plik nagłówka jest dołączany jako szczegóły implementacji, które nie powinny być częścią interfejsu modułu. Mogą istnieć zaawansowane przypadki, w których chcesz to zrobić, ale ogólnie nie. Żadne oddzielne metadane (.ifc pliki) nie są generowane dla #include dyrektyw w ramach fragmentu modułu globalnego. Fragmenty modułów globalnych zapewniają dobre miejsce do uwzględnienia plików nagłówków, takich jak windows.h, lub w systemie Linux. unistd.h

Kompilowanie pliku implementacji modułu nie zawiera żadnych bibliotek, ponieważ nie wymaga ich w ramach implementacji. Ale jeśli tak, ten obszar jest miejscem, w którym #include dyrektywy pójdą.

Wiersz module BasicPlane.Figures:Rectangle; wskazuje, że ten plik jest częścią nazwanego modułu BasicPlane.Figures. Kompilator automatycznie przenosi typy i funkcje uwidocznione przez interfejs modułu podstawowego do tego pliku. Jednostka implementacji modułu nie ma słowa kluczowego exportmodule przed słowem kluczowym w deklaracji modułu.

Poniżej przedstawiono definicję funkcji area(), height()i width(). Zostały one zadeklarowane w Rectangle partycji w BasicPlane.Figures-Rectangle.ixxpliku . Ponieważ podstawowy interfejs modułu dla tego modułu zaimportował Point partycje modułu i , Rectangle te typy są widoczne tutaj w pliku implementacji jednostki modułu. Interesująca funkcja jednostek implementacji modułu: kompilator automatycznie sprawia, że wszystko w odpowiednim interfejsie podstawowym modułu jest widoczne dla pliku. Nie imports <module-name> jest potrzebne.

Wszystkie zadeklarowane elementy w ramach jednostki implementacji są widoczne tylko dla modułu, do którego należy.

Importowanie modułu

Teraz użyjemy zdefiniowanego modułu. Otwórz plik ModulesTutorial.cpp. Został on utworzony automatycznie w ramach projektu. Obecnie zawiera funkcję main(). Zastąp jego zawartość:

#include <iostream>

import BasicPlane.Figures;

int main()
{
    Rectangle r{ {1,8}, {11,3} };

    std::cout << "area: " << area(r) << '\n';
    std::cout << "width: " << width(r) << '\n';

    return 0;
}

import BasicPlane.Figures; Instrukcja sprawia, że wszystkie wyeksportowane funkcje i typy z modułu BasicPlane.Figures są widoczne dla tego pliku. Może to nadejść przed lub po jakichkolwiek dyrektywach #include .

Następnie aplikacja używa typów i funkcji z modułu do wyprowadzania obszaru i szerokości zdefiniowanego prostokąta:

area: 50
width: 10

Anatomia modułu

Teraz przyjrzyjmy się dokładniej różnym plikom modułów.

Interfejs modułu podstawowego

Moduł składa się z co najmniej jednego pliku. Jeden z nich definiuje interfejs, który będą widzieć importerzy. Ten plik zawiera interfejs modułu podstawowego. Dla każdego modułu może istnieć tylko jeden podstawowy interfejs modułu. Jak wspomniano wcześniej, wyeksportowana jednostka interfejsu modułu nie określa partycji modułu.

Domyślnie ma .ixx rozszerzenie. Można jednak traktować plik źródłowy z dowolnym rozszerzeniem jako plik interfejsu modułu. W tym celu ustaw właściwość Compile As na karcie Zaawansowane strony właściwości pliku źródłowego na wartość Compile As Module (/interface):

Screenshot of a hypothetical source file's Configuration properties under Configuration properties > C/C++ > Advanced > Compile As, with Compile as C++ Module Code (/interface) highlighted

Podstawowy konspekt pliku definicji interfejsu modułu to:

module; // optional. Defines the beginning of the global module fragment

// #include directives go here but only apply to this file and
// aren't shared with other module implementation files.
// Macro definitions aren't visible outside this file, or to importers.
// import statements aren't allowed here. They go in the module preamble, below.

export module [module-name]; // Required. Marks the beginning of the module preamble

// import statements go here. They're available to all files that belong to the named module
// Put #includes in the global module fragment, above

// After any import statements, the module purview begins here
// Put exported functions, types, and templates here

module :private; // optional. The start of the private module partition.

// Everything after this point is visible only within this file, and isn't 
// visible to any of the other files that belong to the named module.

Ten plik musi zaczynać się od , module; aby wskazać początek fragmentu modułu globalnego lub export module [module-name]; wskazać początek modułu purview.

Moduł purview to miejsce, w którym funkcje, typy, szablony itd. są dostępne w module.

Jest to również miejsce, w którym można uwidocznić inne moduły lub partycje modułów za pomocą export import słów kluczowych, jak pokazano w BasicPlane.Figures.ixx pliku.

Plik interfejsu podstawowego musi wyeksportować wszystkie partycje interfejsu zdefiniowane bezpośrednio lub pośrednio dla modułu albo program jest źle sformułowany.

Partycja modułu prywatnego umożliwia umieszczenie elementów, które mają być widoczne tylko w tym pliku.

Jednostki interfejsu modułu poprzedzają słowo kluczowe module słowo kluczowe export.

Aby uzyskać bardziej szczegółowe informacje na temat składni modułu, zobacz Moduły.

Jednostki implementacji modułu

Jednostki implementacji modułu należą do nazwanego modułu. Nazwany moduł, do którego należy, jest wskazywany przez instrukcję module [module-name] w pliku . Jednostki implementacji modułu zawierają szczegóły implementacji, które ze względów higieny kodu lub innych powodów nie są umieszczane w interfejsie modułu podstawowego ani w pliku partycji modułu.

Jednostki implementacji modułu są przydatne do podzielenia dużego modułu na mniejsze elementy, co może spowodować szybsze czasy kompilacji. Ta technika jest krótko omówiona w sekcji Najlepsze rozwiązania .

Pliki jednostki implementacji modułu mają .cpp rozszerzenie. Podstawowy konspekt pliku jednostki implementacji modułu to:

// optional #include or import statements. These only apply to this file
// imports in the associated module's interface are automatically available to this file

module [module-name]; // required. Identifies which named module this implementation unit belongs to

// implementation

Pliki partycji modułu

Partycje modułów umożliwiają składnikowanie modułu na różne elementy lub partycje. Partycje modułu mają być importowane tylko w plikach, które są częścią nazwanego modułu. Nie można ich zaimportować poza nazwanym modułem.

Partycja ma plik interfejsu i zero lub więcej plików implementacji. Partycja modułu współdzieli własność wszystkich deklaracji w całym module.

Wszystkie nazwy wyeksportowane przez pliki interfejsu partycji muszą zostać zaimportowane i ponownie wyeksportowane (export import) przez plik interfejsu podstawowego. Nazwa partycji musi zaczynać się od nazwy modułu, a następnie dwukropka, a następnie nazwy partycji.

Podstawowy konspekt pliku interfejsu partycji wygląda następująco:

module; // optional. Defines the beginning of the global module fragment

// This is where #include directives go. They only apply to this file and aren't shared
// with other module implementation files.
// Macro definitions aren't visible outside of this file or to importers
// import statements aren't allowed here. They go in the module preamble, below

export module [Module-name]:[Partition name]; // Required. Marks the beginning of the module preamble

// import statements go here. 
// To access declarations in another partition, import the partition. Only use the partition name, not the module name.
// For example, import :Point;
// #include directives don't go here. The recommended place is in the global module fragment, above

// export imports statements go here

// after import, export import statements, the module purview begins
// put exported functions, types, and templates for the partition here

module :private; // optional. Everything after this point is visible only within this file, and isn't 
                         // visible to any of the other files that belong to the named module.
...

Najlepsze rozwiązania dotyczące modułów

Moduł i kod importujący go muszą zostać skompilowane przy użyciu tych samych opcji kompilatora.

Nazewnictwo modułów

  • Możesz użyć krosek ('.') w nazwach modułów, ale nie mają specjalnego znaczenia dla kompilatora. Użyj ich, aby przekazać znaczenie użytkownikom modułu. Na przykład zacznij od biblioteki lub głównej przestrzeni nazw projektu. Zakończ z nazwą opisającą funkcjonalność modułu. BasicPlane.Figures ma na celu przekazanie interfejsu API dla płaszczyzn geometrycznych, a w szczególności rysunków, które mogą być reprezentowane na płaszczyźnie.
  • Nazwa pliku, który zawiera interfejs podstawowy modułu, jest ogólnie nazwą modułu. Na przykład, biorąc pod uwagę nazwę modułu , nazwa BasicPlane.Figurespliku zawierającego interfejs podstawowy ma nazwę BasicPlane.Figures.ixx.
  • Nazwa pliku partycji modułu to zwykle <primary-module-name>-<module-partition-name> miejsce, w którym następuje nazwa modułu, a następnie łącznik ('-'), a następnie nazwa partycji. Na przykład BasicPlane.Figures-Rectangle.ixx

Jeśli tworzysz z poziomu wiersza polecenia i używasz tej konwencji nazewnictwa dla partycji modułu, nie trzeba jawnie dodawać /reference dla każdego pliku partycji modułu. Kompilator wyszuka je automatycznie na podstawie nazwy modułu. Nazwa skompilowanego pliku partycji (kończącego się .ifc rozszerzeniem) jest generowana na podstawie nazwy modułu. Rozważmy nazwę BasicPlane.Figures:Rectanglemodułu : kompilator przewiduje, że odpowiedni skompilowany plik partycji dla Rectangle elementu ma nazwę BasicPlane.Figures-Rectangle.ifc. Kompilator używa tego schematu nazewnictwa, aby ułatwić korzystanie z partycji modułów przez automatyczne znajdowanie plików jednostek interfejsu dla partycji.

Można je nazwać przy użyciu własnej konwencji. Następnie należy jednak określić odpowiednie /reference argumenty kompilatora wiersza polecenia.

Moduły factor

Użyj plików implementacji modułu i partycji, aby uwzględnić moduł w celu ułatwienia konserwacji kodu i potencjalnie krótszych czasów kompilacji.

Na przykład przeniesienie implementacji modułu z pliku definicji interfejsu modułu i do pliku implementacji modułu oznacza, że zmiany implementacji niekoniecznie spowodują, że każdy plik, który importuje moduł do ponownego kompilowania (chyba że masz inline implementacje).

Partycje modułów ułatwiają logiczne uwzględnianie dużego modułu. Mogą one służyć do poprawy czasu kompilacji, aby zmiany w części implementacji nie powodowały ponownego kompilowania wszystkich plików modułu.

Podsumowanie

W tym samouczku przedstawiono podstawy modułów języka C++20. Utworzono podstawowy interfejs modułu, zdefiniowano partycję modułu i utworzono plik implementacji modułu.

Zobacz też

Omówienie modułów w języku C++
module, , importexport słowa kluczowe
Przewodnik po modułach języka C++ w programie Visual Studio
Praktyczne moduły języka C++20 i przyszłość narzędzi wokół modułów języka C++
Przenoszenie projektu do języka C++ o nazwie Modules
Przewodnik: kompilowanie i importowanie jednostek nagłówków w programie Microsoft Visual C++