File: intro.yo

package info (click to toggle)
c%2B%2B-annotations 13.02.02-1
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 13,576 kB
  • sloc: cpp: 25,297; makefile: 1,523; ansic: 165; sh: 126; perl: 90; fortran: 27
file content (111 lines) | stat: -rw-r--r-- 6,523 bytes parent folder | download | duplicates (2)
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
Since the introduction of header files in the bf(C) programming language
header files have been the main tool for declaring elements that are not
defined but are used in source files. E.g., to use tt(printf) in tt(main)
the preprocessor directive tt(#include <stdio.h>) had to be specified.

Header files are still extensively used in bf(C++), but gradually some
drawbacks emerged. One minor drawback is that, in bf(C++), header files
frequently not merely contain function and variable declarations but often
also type definitions (like class interfaces and enum definitions). When
designing header files the software engineer therefore commonly distinguishes
headers which are merely used inside a local (class) context (like the
internal-header approach advocated in the annotations()) and header files
which are used externally. Those latter header files need include guards to
prevent them from being processed repeatedly by sources (indirectly) including
them. Another, important, drawback is that a header file is processed again
for every source file including it. Such a task is not a trivial one. E.g., if
a header file includes tt(iostream) and tt(string) then that forces a compiler
like tt(g++ 14.2.0) to process over 900,000 bytes of code for every source
file including that header.

To speed up compilations precompiled headers hi(header: precompiled) were
introduced. Although the binary format of precompiled headers does indeed
allow the compiler to parse the content of header files much faster than
their standard text format, they are also very large. A precompiled header
merely including tt(iostream) and tt(string) exceeds 25 MB: a bit more
than its uncompiled text-file equivalent....

Modules were introduced to avoid those complications. Although modules can
still include header files, it's a good design principle to avoid including
header files when designing modules. In general: once a module has been
designed its use doesn't require processing header files anymore, and
consequenly programs that merely use modules are compiled much faster than
corresponding programs that use header files.

There is another, conceptual, feature of modules. The initial high-level
programming languages (like Fortran and Algol) (but also assembly languages)
provided functions (a.k.a. subroutines and procedures) to distinguish
conceptually different task levels.  Functions implement specific tasks. A
program reading data, then processing the data, and finally showing the
results can easily be designed using functions, and is much easier to
understand than replacing the function calls by their actual implementations:
        verb(    int main()
    {
        readData();
        processData();
        showResults();
    }
        )
    Often such functions use their own support functions, etc, etc, until
trivial decomposition levels are reached where simple flow control and
expression statements are used.

This decomposition methodology works very good. It still does. But at the
em(global) level a problem does exist: there's little integrity
protection. Function parameters may help to maintain the program's data
integrity, but it's difficult to ensure the integrity of global data.

In this respect em(classes) do a much better job. Their tt(private) sections
offer means for class-designers to guarantee the integrity of the classes'
data.

Modules allow us to take the next step up the (separation or integrity)
ladder. Conceptually modules offer program sections which are completely
separated from the rest of the program. Modules define what the outside world
can use and reach, whether they are variables, functions, or types (like
classes). Modules resemble factories: visitors can go to showrooms and meeting
halls, but the locations where the actual products are being designed and
constructed are not open to the public.

In this chapter we cover the syntax, design, and implementation of modules as
offered by the bf(C++) programming language. To use modules with the current
edition of the Gnu tt(g++) compiler (version 14.2.0) tt(--std=c++26) (or more
recent) should be specified as well as the hi(module flag) module compilation
flag ti(-fmodules-ts). E.g.,
        verb(    g++ -c --std=c++26 -fmodules-ts -Wall modsource.cc)

Unfortunately, currently it's not all sunshine and roses. One (current?)
consequence of using modules is that the standard that was specified when
compiling those modules is also required when compiling sources using those
modules. If the specified standards differ (e.g., the modules were compiled
with option tt(--std=c++26), but for a source file using those modules
tt(--std=c++23) was specified) then the compilation fails with an error like
    verb(    error: language dialect differs 'C++26/coroutines', expected 'C++23/coroutines'
    )
    A similar error is reported when the modules were compiled with
tt(--std=c++23) and the module using source file is compiled specifying
tt(--std=c++26). Therefore, once a new standard becomes available, and a
module defining source files is recompiled using the new standard then module
source files using that module must also be recompiled using that standard.

At lurl(https://gcc.gnu.org/bugzilla/show_bug.cgi?id=103524) an overview is
published of the (many) currently reported compiler bugs when modules are
compiled by Gnu's tt(g++) compiler. Many of those bugs refer to internal
compiler errors, sometimes very basic code that correctly compiles when
modules are not used but that won't compile when using modules. Sometimes
reported errors are completely incomprehensible. Another complexity is
introduced by the fact that, e.g., a class which is defined inside a module is
no longer declared in an interface providing header file. Instead, it is
defined in a module defining source file. Consequently, those module-interface
defining source files must be compiled before the member functions of such a
class can be compiled. But it's not the module's interface's object file
that's important at that point. When the module-interface defining source file
is compiled the compiler defines a tt(`module-name'.gcm) file in a
sub-directory tt(gcm.cache) (cf. section ref(GCMCACHE)). Whenever a source
file that uses the module is compiled the compiler must be able to read that
tt(gcm.cache/module-name.gcm) file. As a software engineer you cannot simply
compile such module using source files, but em(you) must ensure that the
compiler has access to the proper tt(module-name.gcm) files.