1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
Modules (and partitions, cf. section ref(PARTITIONS)) are designed using
source files instead of header files. A module always defines a
emi(module interface unit). The module's interface unit is the module's
equivalent of a header file. Consider (as used in the annotations()) defining
the module's interface in a file named `tt(modname.cc)' (where tt(name) is the
(possibly lowercase) module's name), located in a sub-directory having the
(possibly lowercase) module's name. Using plain (internal) header files should
be avoided when defining and/or using modules.
Here's an example of a module's interface. The module's name is tt(Square) and
it declares a function, a class, and a variable:
verbinclude(-as4 examples/initial/square/modsquare.cc)
This module interface merely serves as an illustration. In practice module
interfaces don't contain many different items, but usually just a single
class or, alternatively, a series of utility functions. For now, however, the
slightly overpopulated module tt(Square) is used as an initial illustration.
The interface's top-line exports and defines the module name. This must be the
first line of the module's interface unit. Next, the function tt(square) and
tt(class Square) are declared inside an
hi(export compound) tt(export) compound. It is possible to export
componentsindividually, but using an `export compound' is convenient. Exported
components can be used outside of the module. Non-exported components (like
tt(g_squared)) are only available to the module's components, and are
therefore like global components, albeit with a restricted (module) scope.
Also note that the variable tt(g_squared) is listed in the interface as
tt(extern double g_squared): it is therefore a em(declaration), not a
definition. To em(define) variables in a module omit tt(extern) (as in
tt(double g_squared)), but to avoid overpopulating modules it's advised to
merely put declarations in module interface units.
The tt(modsquare.cc) file can now be compiled, requiring the tt(-fmodules-ts)
compiler option:
verb( g++ -c --std=c++26 -fmodules-ts -Wall modsquare.cc)
Compiling tt(modsquare.cc) not only produces the file tt(modsquare.o), but
also a sub-directory ti(gcm.cache), containing the `module compiled interface
file' tt(Square.gcm), which is somewhat comparable to a traditional
pre-compiled header file. This tt(.gcm) file must be available when compiling
source files implementing components of module tt(Square), and it must also be
available to other source files that em(import) (i.e., use) module
tt(Square). Consequently, the directories containing such files must therefore
also have tt(gcm.cache) sub-directories containing the tt(Square.gcm)
file. This requirement is complex, and in practice a so-called em(modmapper)
(cf. section ref(MODMAPPER)) is used to handle the complexity. A simple way to
make module compiled interface files available to all sections of a project is
by defining a top-level directory tt(gcm.cache) and using soft-links (like
tt(square/gcm.cache)) to the top-level's tt(gcm.cache) directory. The
top-level tt(gcm.cache) directory and the soft-links in the project's
sub-directories can be prepared before compiling tt(square/modsquare.cc)
resulting in the following directory structure:
verb(
.
+-- gcm.cache
| +-- Square.gcm
|
+-- square
+-- gcm.cache -> ../gcm.cache
)
All components of the module tt(Square) must specify that they're part of
that module. Traditionally this is realized by using (internal) header
files. But projects using modules should no longer need header files. Instead
of the (traditionally used) tt(square.h) and tt(square.ih) files a module
hi(module: frame) em(frame) file, tailored to the modules' requirements
can be used when defining source files belonging to modules. In this example
the requirement for the remaining source files of the tt(Square) module is
simple: just specify that the source file belongs to the tt(Square)
module. Here's a basic tt(frame) file, tailored to the members of the tt(class
Square):
verb( module Square;
Square::()
{
}
)
The em(function) square isn't part of the tt(class Square), so when it's
defined the tt(Square::) scope is omitted:
verbinclude(-as4 examples/initial/square/square.cc)
But the members of the tt(class Square) can be defined as usual after copying
the tt(frame) file to the source filew defining those members. Here is
tt(Square's) constructor:
verbinclude(-as4 examples/initial/square/square1.cc)
and the other members are defined analogously:
verbinclude(-as4 examples/initial/square/amount1.cc)
verbinclude(-as4 examples/initial/square/amount2.cc)
verbinclude(-as4 examples/initial/square/lastsquared.cc)
verbinclude(-as4 examples/initial/square/square.cc)
As an aside: the members of this class tt(Square) are all very simple, and
instead of defining them in separate source files they em(could) also be
defined tt(inline) in the tt(module.cc) file itself. Since the class
tt(Square's) interface is exported its members are too, and inline
implementations of its members don't have to be provided in the export
compound.
The module can now be used by, e.g., the program's tt(main) function. Source
files importing a module must emi(import) that module, and if multiple source
files are defined in the top-level directory (like tt(main.cc, usage.cc,)
etc.), that directory can define its own tt(frame) file. In this initial
example there's only a single tt(main.cc) source file, which merely has to
import module tt(Square). But since it also uses tt(std::cout) it must import
the
emi(module compiled system header) tt(iostream) (cf. section ref(MODHDR)):
verbinclude(-as4 examples/initial/main.cc)
The tt(gcm.cache) directories are only required during compilation time. The
em(linker) doesn't use them and once the source files have been compiled a
program can be constructed as before, by linking the object files, resulting
in a binary program.
This example illustrates several characteristics of modules:
itemization(
it() module interfaces start with tt(export module) followed by the
module's name. Module names are identifiers, possibly followed by
`tt(. identifier)' sequences (like tt(My.Module)).
it() module names containing a colon (like tt(:name) or tt(name1:name2))
are also encountered. Such names refer to module em(partitions),
covered in section ref(PARTITIONS).
it() module interface units em(must) begin with an tt(export module
'module-name') line. Therefore module interface files cannot define
multiple modules;
it() components which are accessible to sources outside of the module
itself must be specified in an tt(export) compound or must be
specified using their own initial tt(export) keyword. If no tt(export)
is used the component is only accessible to the components of the
module;
it() When using modules project header files are not used anymore. Module
compiled system headers (like in tt(import <iostream>;)) em(are)
used, but preprocessor directives like tt(#include <iostream>) aren't.
it() Since module interface units are source files include guards aren't
required anymore.
it() Since header files are not used anymore the compiler doesn't have to
read thousands of lines of header files anymore, significantly
reducing compilation times.
)
|