12.translation Units II
12.translation Units II
Programming
12. Translation Units II
Include, Module, and Namespace
Federico Busato
2023-11-14
Table of Context
1 #include Issues
Include Guard
Forward Declaration
Circular Dependencies
Common Linking Errors
1/54
Table of Context
2 C++20 Modules
Overview
Terminology
Visibility and Reachability
Module Unit Types
Keywords
Global Module Fragment
Private Module Fragment
Header Module Unit
Module Partitions
2/54
Table of Context
3 Namespace
Namespace Functions vs. Class + static Methods
Namespace Alias
Anonymous Namespace
inline Namespace
Attributes for Namespace
3/54
Table of Context
4/54
#include Issues
Include Guard 1/3
The include guard avoids the problem of multiple inclusions of a header file in a
translation unit
header.hpp:
# ifndef HEADER_HPP // include guard
# define HEADER_HPP
# endif // HEADER_HPP
#pragma once preprocessor directive is an alternative to the the include guard to force
current file to be included only once in a translation unit
• #pragma once is less portable but less verbose and compile faster than the include
guard
Common case:
6/54
Include Guard 3/3
header A.hpp:
# pragma once // prevent "multiple definitions" linking error
struct A {
};
header B.hpp:
# include "header_A.hpp" // included here
struct B {
A a;
};
main.cpp:
# include "header_A.hpp" // .. and included here
# include "header_B.hpp"
int main() {
A a; // ok, here we need "header_A.hpp"
B b; // ok, here we need "header_B.hpp"
7/54
}
Forward Declaration
int main() {
f(); // ok, f() is defined in the translation unit
// A a; // compiler error no definition (incomplete type)
// e.g. the compiler is not able to deduce the size of A
A* a; // ok
}
Advantages:
• Forward declarations can save compile time as #include forces the compiler to open
more files and process more input
• Forward declarations can save on unnecessary recompilation. #include can force your
code to be recompiled more often, due to unrelated changes in the header
Disadvantages:
• Forward declarations can hide a dependency, allowing user code to skip necessary
recompilation when headers change
• Forward declaring multiple symbols from a header can be more verbose than simply
#including the header
9/54
google.github.io/styleguide/cppguide.html#Forward Declarations
Circular Dependencies 1/3
header A.hpp:
# pragma once // first include
# include "header_B.hpp"
class A {
B* b;
};
header B.hpp:
# pragma once // second include
# include "header_C.hpp"
class B {
C* c;
};
header C.hpp:
# pragma once // third include
# include "header_A.hpp"
class C { // compile error "header_A.hpp": already included by "main.cpp"
A* a; // the compiler does not know the meaning of "A"
11/54
};
Circular Dependencies (fix) 3/3
header A.hpp:
# pragma once
class B; // forward declaration
// note: does not include "header_B.hpp"
class A {
B* b;
};
header B.hpp:
# pragma once
class C; // forward declaration
class B {
C* c;
};
header C.hpp:
# pragma once
class A; // forward declaration
class C {
A* a;
}; 12/54
Common Linking Errors
• undefined reference
Solutions:
- Check if the right headers and sources are included
- Break circular dependencies (could be hard to find)
• multiple definitions
Solutions:
- inline function, variable definition or extern declaration
- Add include guard/ #pragma once to header files
- Place template definition in header file and full specialization in source files
13/54
C++20 Modules
C++20 Modules 1/2
The #include problem: The duplication of work - the same header files are
possibly parsed/compiled multiple times and most of the compiled output is later-on
thrown away again by the linker
C++20 introduces modules as a robust replacement for plain #include
Module (C++20)
A module is a set of source code files that are compiled independently of the
translation units that import them
Visibility of names instructs the linker if a symbol can be used by another translation
unit. Visible also means a candidate for name lookup
17/54
Reachability Example
Common example: the members of a class are reachable (i.e. can be used) or the class
size is known, but not the class type itself
auto g() {
struct A {
void f() {}
};
return A{};
}
//---------------------------------------------------------------------------------
auto x = g(); // ok
// A y = g(); // compile error, "A" is unknown at this point
x.f(); // ok
sizeof(x); // ok
using T = decltype(x); // ok
18/54
Module Unit Types
• A module interface unit is a module unit that exports a symbol and/or module
name or module partition name
• A primary module interface unit is a module interface unit that exports the
module name. There must be one and only one primary module interface unit in
a module
• A module implementation unit is a module unit that does not export a module
name or module partition name
A module interface unit should contain only declarations if one or more module
implementation units are present. A module implementation unit
implements/defines the declarations of module interface units
19/54
Keywords
import makes a module and its symbols visible in the current file
import my.module; // after module declaration and #include
export makes symbols visible to the files that import the current module
• export module <module name> makes visible all the exported symbols of a
module. It must appear once per module in the primary module interface unit
• export namespace <namespace> makes visible all symbols in a namespace
• export <entity> makes visible a specific function, class, or variable
• export {<code>} makes visible all symbols in a block 20/54
import Example
# include <iostream>
int main() {
std::cout << "Hello World";
}
Preprocessing size -E : ∼1MB
import <iostream>
int main() {
std::cout << "Hello World";
}
Preprocessing size: 236B (x500)
Compile time: 2x (up to 10x) less
g++-12 -std=c++20 -fmodules-ts main.cpp -x c++-system-header iostream
21/54
export Example - Single Primary Module Interface Unit
my module.cpp
export module my.example; // make visible all module symbols
22/54
export Example - Two Module Interface Units
import
• A module implementation unit can import another module, but cannot
export any names. Symbols of the module interface unit are imported implicitly
• All import must appear before any declarations in that module unit and after
module; a export module (if present)
export
• Symbols with internal linkage or no linkage cannot be exported, i.e. anonymous
namespaces and static entities
import main_module;
int main() {
f(); // ok, f() is visible
}
26/54
Global Module Fragment
A global module fragment (unnamed module) can be used to include header files in
a module interface when importing them is not possible or preprocessing directives are
needed
module; // start Global Module Fragment
# define ENABLE_FAST_MATH
# include "my_math.h"
Macro definitions or other preprocessing directives are not visible outside the file itself
27/54
Private Module Fragment
• Macros from the header are available for the importer, but macros defined in the
importer have no effect on the imported header
• Importing compiled declarations is faster than #include
29/54
Module Partitions 1/2
• Declarations in any of the partitions are visible within the entire module
main module.ixx
export module main_module;
partition1.ixx
export module module_name:partition1;
partition2.ixx
export module module_name:partition2;
The problem: Named entities, such as variables, functions, and compound types declared
outside any block has global scope, meaning that its name is valid anywhere in the code
Namespaces allow to group named entities that otherwise would have global
scope into narrower scopes, giving them namespace scope (where std stands
for “standard”)
Namespaces provide a method for preventing name conflicts in large projects. Symbols
declared inside a namespace block are placed in a named scope that prevents them
from being mistaken for identically-named symbols in other scopes
32/54
Namespace Functions vs. Class + static Methods
Namespace functions:
• Namespace can be extended anywhere (without control)
• Namespace specifier can be avoided with the keyword using
namespace ns2 {
void f() {
std::cout << "ns2" << std::endl;
}
} // namespace ns2
int main () {
ns1::f(); // print "ns1"
ns2::f(); // print "ns2"
// f(); // compile error f() is not visible 34/54
}
Namespace Example 2
# include <iostream>
namespace ns1 {
void f() { std::cout << "ns1::f()" << endl; }
} // namespace ns1
int main () {
ns1::f(); // print "ns1::f()"
ns1::g(); // print "ns1::g()"
}
35/54
‘using namespace’ Declaration
# include <iostream>
void f() { std::cout << "global" << endl; }
namespace ns1 {
void f() { std::cout << "ns1::f()" << endl; }
void g() { std::cout << "ns1::g()" << endl; }
} // namespace ns1
int main () {
f(); // ok, print "global"
using namespace ns1; // expand "ns1" in this scope (from this line)
g(); // ok, print "ns1::g()", only one choice
// f(); // compile error ambiguous function name
::f(); // ok, print "global"
ns1::f(); // ok, print "ns1::f()"
}
36/54
Nested Namespaces
# include <iostream>
namespace ns1 {
void f() { std::cout << "ns1::f()" << endl; }
namespace ns2 {
void f() { std::cout << "ns1::ns2::f()" << endl; }
} // namespace ns2
} // namespace ns1
37/54
Namespace Alias
int main() {
namespace ns = very_very_long_namespace; // namespace alias
ns::g(); // available only in this scope
}
38/54
Anonymous Namespace
} // namespace ns1
using namespace ns1;
void f() {}
} // namespace ns1
41/54
Compiling Multiple
Translation Units
Fundamental Compiler Flags
42/54
Compile Methods
Method 1
Compile all files together (naive):
g++ main.cpp source.cpp -o main.out
Method 2
Compile each translation unit in a file object:
g++ -c source.cpp -o source.o
g++ -c main.cpp -o main.o
Multiple objects can be compiled in parallel
Link all file objects:
g++ main.o source.o -o main.out
43/54
C++ Libraries 1/2
A static library is a set of object files (just the concatenation) that are directly linked
into the final executable. If a program is compiled with a static library, all the
functionality of the static library becomes part of final executable
– A static library cannot be modified without re-link the final executable
– Increase the size of the final executable
+ The linker can optimize the final executable (link time optimization)
A dynamic library, also called a shared library, consists of routines that are loaded
into the application at run-time. If a program is compiled with a dynamic library, the
library does not become part of final executable. It remains as a separate unit
+ A dynamic library can be modified without re-link
– Dynamic library functions are called outside the executable
– Neither the linker, nor the compiler can optimize the code between shared libraries
and the final executable
• The environment variables must be set to the right shared library path, otherwise
the application crashes at the beginning
Specify the library path (path where search for static/dynamic libraries) to the
compiler: g++ -L<library path> main.cpp -o main
46/54
Deal with Libraries
• PATH Specify the directories where search for dynamic/shared libraries .dll at
run-time
47/54
Build Static/Dynamic Libraries
Name mangling is a technique used to solve various problems caused by the need to
resolve unique names
Transforming C++ ABI (Application binary interface) identifiers into the original
source identifiers is called demangling
Example (linking error):
_ZNSt13basic_filebufIcSt11char_traitsIcEED1Ev
After demangling:
std::basic_filebuf<char, std::char_traits<char> >::∼basic_filebuf()
The ldd utility shows the shared objects (shared libraries) required by a program or
other shared objects
$ ldd /bin/ls
linux-vdso.so.1 (0x00007ffcc3563000)
libselinux.so.1 => /lib64/libselinux.so.1 (0x00007f87e5459000)
libcap.so.2 => /lib64/libcap.so.2 (0x00007f87e5254000)
libc.so.6 => /lib64/libc.so.6 (0x00007f87e4e92000)
libpcre.so.1 => /lib64/libpcre.so.1 (0x00007f87e4c22000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007f87e4a1e000)
/lib64/ld-linux-x86-64.so.2 (0x00005574bf12e000)
libattr.so.1 => /lib64/libattr.so.1 (0x00007f87e4817000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f87e45fa000)
50/54
Find Object/Executable Symbols 1/3
The nm utility provides information on the symbols being used in an object file or
executable file
$ nm -D -C something.so
w __gmon_start__
D __libc_start_main
D free
D malloc
D printf
51/54
Find Object/Executable Symbols 2/3
52/54
Find Object/Executable Symbols 3/3
53/54
References and Additional Material
54/54