Lecture Slides For Programming in Cpp-2021!04!01
Lecture Slides For Programming in Cpp-2021!04!01
Michael D. Adams
Copyright © 2015, 2016, 2017, 2018, 2019, 2020, 2021 Michael D. Adams
This document is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported (CC BY-NC-ND 3.0) License. A copy
of this license can be found on page iii of this document. For a simple explanation of the rights granted by this license, see:
http://creativecommons.org/licenses/by-nc-nd/3.0/
UNIX and X Window System are registered trademarks of The Open Group. Windows is a registered trademark of Microsoft Corporation.
Fedora is a registered trademark of Red Hat, Inc. Ubuntu is a registered trademark of Canonical Ltd. MATLAB is a registered trademark of The
MathWorks, Inc. OpenGL is a registered trademark of Hewlett Packard Enterprise. The YouTube logo is a registered trademark of Google, Inc.
The GitHub logo is a registered trademark of GitHub, Inc. The Twitter logo is a registered trademark of Twitter, Inc.
License who has not previously violated the terms of this License with
respect to the Work, or who has received express permission from the
Licensor to exercise rights under this License despite a previous
violation.
h. "Publicly Perform" means to perform public recitations of the Work and
to communicate to the public those public recitations, by any means or
process, including by wire or wireless means or public digital
performances; to make available to the public Works in such a way that
members of the public may access these Works from a place and at a
place individually chosen by them; to perform the Work to the public
by any means or process and the communication to the public of the
performances of the Work, including by public digital performance; to
broadcast and rebroadcast the Work by any means including signs,
sounds or images.
i. "Reproduce" means to make copies of the Work by any means including
without limitation by sound or visual recordings and the right of
fixation and reproducing fixations of the Work, including storage of a
protected performance or phonogram in digital form or other electronic
medium.
2. Fair Dealing Rights. Nothing in this License is intended to reduce,
limit, or restrict any uses free from copyright or rights arising from
limitations or exceptions that are provided for in connection with the
copyright protection under copyright law or other applicable laws.
3. License Grant. Subject to the terms and conditions of this License,
Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
perpetual (for the duration of the applicable copyright) license to
exercise the rights in the Work as stated below:
a. to Reproduce the Work, to incorporate the Work into one or more
Collections, and to Reproduce the Work as incorporated in the
Collections; and,
b. to Distribute and Publicly Perform the Work including as incorporated
in Collections.
The above rights may be exercised in all media and formats whether now
known or hereafter devised. The above rights include the right to make
such modifications as are technically necessary to exercise the rights in
other media and formats, but otherwise you have no rights to make
Adaptations. Subject to 8(f), all rights not expressly granted by Licensor
are hereby reserved, including but not limited to the rights set forth in
Section 4(d).
4. Restrictions. The license granted in Section 3 above is expressly made
subject to and limited by the following restrictions:
a. You may Distribute or Publicly Perform the Work only under the terms
of this License. You must include a copy of, or the Uniform Resource
Identifier (URI) for, this License with every copy of the Work You
Distribute or Publicly Perform. You may not offer or impose any terms
on the Work that restrict the terms of this License or the ability of
the recipient of the Work to exercise the rights granted to that
recipient under the terms of the License. You may not sublicense the
Work. You must keep intact all notices that refer to this License and
to the disclaimer of warranties with every copy of the Work You
Distribute or Publicly Perform. When You Distribute or Publicly
Perform the Work, You may not impose any effective technological
measures on the Work that restrict the ability of a recipient of the
Work from You to exercise the rights granted to that recipient under
the terms of the License. This Section 4(a) applies to the Work as
incorporated in a Collection, but this does not require the Collection
apart from the Work itself to be made subject to the terms of this
License. If You create a Collection, upon notice from any Licensor You
must, to the extent practicable, remove from the Collection any credit
as required by Section 4(c), as requested.
b. You may not exercise any of the rights granted to You in Section 3
above in any manner that is primarily intended for or directed toward
commercial advantage or private monetary compensation. The exchange of
the Work for other copyrighted works by means of digital file-sharing
or otherwise shall not be considered to be intended for or directed
toward commercial advantage or private monetary compensation, provided
there is no payment of any monetary compensation in connection with
the exchange of copyrighted works.
c. If You Distribute, or Publicly Perform the Work or Collections, You
must, unless a request has been made pursuant to Section 4(a), keep
intact all copyright notices for the Work and provide, reasonable to
the medium or means You are utilizing: (i) the name of the Original
Author (or pseudonym, if applicable) if supplied, and/or if the
Original Author and/or Licensor designate another party or parties
(e.g., a sponsor institute, publishing entity, journal) for
attribution ("Attribution Parties") in Licensor’s copyright notice,
Preface
■ Many aspects of the C++ language are covered from introductory to more
advanced.
■ Some aspects of the C++ standard library are also introduced.
■ In addition, various general programming-related topics are considered.
■ These slides are intended to be used in conjunction with the following
book:
2 M. D. Adams, Exercises for Programming in C++ (Version 2021-04-01),
University of Victoria, Victoria, BC, Canada, Apr. 2021, xxii + 136 pages,
ISBN 978-0-9879197-5-5 (PDF). Available from Google Books, Google Play
Books, and author’s web site
http://www.ece.uvic.ca/~mdadams/cppbook.
■ The author would like to thank Robert Leahy for reviewing various drafts of
many of these slides and providing many useful comments that allowed
the quality of these materials to be improved significantly.
■ The author of the lecture slides maintains a companion web site for the
slides.
■ The most recent version of the slides can be downloaded from this site.
■ Additional information related to the slides is also available from this site,
including:
2 errata for the slides; and
2 information on the companion web site, companion Git repository, and
companion YouTube channel for the slides.
■ The URL of this web site is:
2 http://www.ece.uvic.ca/~mdadams/cppbook
■ The author has prepared video lectures for some of the material covered
in these slides and the associated book.
■ All of the videos are hosted by YouTube and available through the author’s
YouTube channel:
2 https://www.youtube.com/iamcanadian1867
■ For more information about the SDE, refer to the main repository page on
GitHub.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 xxii
Virtual Machine (VM) Disk Images for the SDE
Software
■ developed by Tom Love and Brad Cox of Stepstone (later bought by NeXT
and subsequently Apple)
■ used primarily on Apple Mac OS X and iOS
■ strict superset of C
■ no official standard that describes Objective C
■ authoritative manual on Objective-C 2.0 available from Apple
■ vendor neutral
■ international standard
■ general purpose
■ powerful yet efficient
■ loosely speaking, includes C as subset; so can learn two languages (C++
and C) for price of one
■ easy to move from C++ to other languages but often not in other direction
■ many other popular languages inspired by C++
■ popular language
■ consistently ranks amongst top languages in TIOBE Software
Programming Community Index
(https://www.tiobe.com/tiobe-index/)
C++
History of C++
Nov 1986 first commercial Cfront PC port (Cfront 1.1, Glockenspiel [in
Ireland])
Feb 1987 Cfront Release 1.2; primarily bug fixes but also added:
■ pointers to members
■ protected members
Oct 1988 first USENIX C++ implementers workshop (Estes Park, CO,
USA)
Jan 1989 first C++ journal “The C++ Report” (from SIGS publications)
started publishing
Jun 1989 Cfront Release 2.0 major cleanup; new features included:
■ multiple inheritance
■ type-safe linkage
■ better resolution of overloaded functions
■ recursive definition of assignment and initialization
■ better facilities for user-defined memory management
■ abstract classes
■ static member functions
■ const member functions
■ protected member functions (first provided in release 1.2)
■ overloading of operator ->
■ pointers to members (first provided in release 1.2)
Apr 1990 Cfront Release 2.1; bug fix release to bring Cfront mostly into
line with ARM
May 1990 annotated reference manual (ARM) published
M. A. Ellis and B. Stroustrup. The Annotated C++
Reference Manual. Addison Wesley, May 1990.
(formed basis for ANSI standardization)
May 1990 first Borland C++ release
Jul 1990 templates accepted (Seattle, WA, USA)
Nov 1990 exceptions accepted (Palo Alto, CA, USA)
Jun 1991 second edition of C++PL published
B. Stroustrup. The C++ Programming Language. Addison
Wesley, 2nd edition, June 1991.
Jun 1991 first ISO WG21 meeting (Lund, Sweden)
Sep 1991 Cfront Release 3.0; added templates (as specified in ARM)
Aug 1994 Standard Template Library (STL) accepted (Waterloo, ON, CA);
described in
A. Stepanov and M. Lee. The standard template library.
Technical Report HPL-94-34 (R.1), HP Labs, Aug. 1994.
Aug 1996 export accepted (Stockholm, Sweden)
1997 third edition of C++PL published
B. Stroustrup. The C++ Programming Language. Addison
Wesley Longman, Reading, MA, USA, 3rd edition, 1997.
Nov 1997 final committee vote on complete standard (Morristown, NJ,
USA)
Jul 1998 Microsoft releases VC++ 6.0, first Microsoft compiler to provide
close-to-complete set of ISO C++
Sep 1998 ISO/IEC 14882:1998 (informally known as C++98) published
ISO/IEC 14882:1998 — programming languages — C++,
Sept. 1998.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 29
Timeline for C84 to C++98 (1982–1998) XI
Apr 2001 motion passed to request new work item: technical report on
libraries (Copenhagen, Denmark); later to become ISO/IEC TR
19768:2007
Oct 2003 ISO/IEC 14882:2003 (informally known as C++03) published;
essentially bug fix release; no changes to language from
programmer’s point of view
ISO/IEC 14882:2003 — programming languages — C++,
Oct. 2003.
2003 work on C++0x (now known as C++11) starts
Oct 2004 estimated number of C++ users 3,270,000
Apr 2005 first votes on features for C++0x (Lillehammer, Norway)
2005 auto, static_assert, and rvalue references accepted in
principle
Apr 2006 first full committee (official) votes on features for C++0x (Berlin,
Germany)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 31
Timeline After C++98 (1998–Present) II
References
Getting Started
1 #include <iostream>
2
3 int main() {
4 std::cout << "Hello, world!\n";
5 return std::cout.flush() ? 0 : 1;
6 }
■ program prints message “Hello, world!” and then exits
■ starting point for execution of C++ program is function called main; every
C++ program must define function called main
■ #include preprocessor directive to include complete contents of file
■ iostream standard header file that defines various types and variables
related to I/O
■ std::cout is standard output stream (defaults to user’s terminal)
■ operator << is used for output
.. .. .. ..
. . . .
■ -pthread
2 enable concurrency support (via pthreads library)
■ -pedantic-errors
2 strictly enforce compliance with standard
■ -Wall
2 enable most warning messages
■ -Wextra
2 enable some extra warning messages not enabled by -Wall
■ -Wpedantic
2 warn about deviations from strict standard compliance
■ -Werror
2 treat all warnings as errors
■ -fno-elide-constructors
2 in contexts where standard allows (but does not require) optimization that
omits creation of temporary, do not attempt to perform this optimization
■ -fconstexpr-loop-limit=n
2 set maximum number of iterations for loop in constexpr functions to n
■ -fconstexpr-depth=n
2 set maximum nested evaluation depth for constexpr functions to n
g++ -c hello.cpp
2 link object file hello.o to produce executable program hello:
g++ -o hello hello.o
■ generally, manual building of program is quite tedious, especially when
program consists of multiple source files and additional compiler options
need to be specified
■ in practice, we use tools to automate build process (e.g., CMake and
Make)
C++ Basics
2 eventCounter
2 sqrt_2
2 f_o_o_b_a_r_4_2
■ identifiers are case sensitive (e.g., counter and cOuNtEr are distinct
identifiers)
■ identifiers cannot be any of reserved keywords (see next slide)
■ scope of identifier is context in which identifier is valid (e.g., block,
function, global)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 58
Reserved Keywords
alignas constexpr mutable switch
alignof constinit namespace template
and const_cast new this
and_eq continue noexcept thread_local
asm decltype not throw
auto default not_eq true
bitand delete nullptr try
bitor do operator typedef
bool double or typeid
break dynamic_cast or_eq typename
case else private union
catch enum protected unsigned
char explicit public using
char8_t export register virtual
char16_t extern reinterpret_cast void
char32_t false requires volatile
class float return wchar_t
co_await for short while
co_return friend signed xor
co_yield goto sizeof xor_eq
compl if static final∗
concept inline static_assert import∗
const int static_cast module∗
consteval long struct override∗
∗ Note: context sensitive
Preprocessor
4 #endif directive
2 signed char
2 signed short int
2 signed int
2 signed long int
2 signed long long int
2 ll or LL
2 both u or U and ll or LL
2 hexadecimal digits for integer part of number (optional if at least one digit
after radix point)
3 period character (i.e., radix point)
4 hexadecimal digits for fractional part of number (optional if at least one digit
before radix point)
5 p character (which designates exponent to follow)
6 one or more decimal digits for base-16 exponent
7 optional floating-point literal suffix (e.g., f or l)
■ examples of hexadecimal floating-point literals:
Literal Type Value (Decimal)
0x.8p0 double 0.5
0x10.cp0 double 16.75
0x.8p0f float 0.5
0xf.fp0f float 15.9375
0x1p10L long double 1024
■ boolean literals:
true
false
■ pointer literal:
nullptr
■ code:
int a[4] = {1, 2, 3, 4};
■ assumptions (for some completely fictitious C++ language
implementation):
2 sizeof(int) is 4
2 array a starts at address 1000
■ memory layout:
Address Name
1000 1 a[0]
1004 2 a[1]
1008 3 a[2]
1012 4 a[3]
■ code:
int i = 42;
int* p = &i;
assert(*p == 42);
■ assumptions (for some completely fictitious C++ language
implementation):
2 sizeof(int) is 4
2 sizeof(int*) is 4
2 &i is ((int*)1000)
2 &p is ((int*)1004)
■ memory layout:
Address Name
1000 42 i
1004 1000 p
■ code:
int i = 42;
int& j = i;
assert(j == 42);
■ assumptions (for some completely fictitious C++ language
implementation):
2 sizeof(int) is 4
2 &i is ((int*)1000)
■ memory layout:
Address Name
1000 42 i, j
■ translation unit: basic unit of compilation in C++ (i.e., single source code
file plus all of its directly and indirectly included header files)
■ extern keyword used to declare object/function in separate translation
unit
■ example:
extern int evil_global_variable;
// declaration only
// actual definition in another file
■ with types that are not pointer or reference types, const can only be
applied to object itself (i.e., top level)
■ that is, object itself may be const or non-const
■ example:
int i = 0; // object i is modifiable
i = 42; // OK: i can be modified
const int ci = 0; // object ci is not modifiable
ci = 42; // ERROR: ci cannot be modified
■ every pointer is associated with two objects: pointer itself and pointee (i.e.,
object to which pointer points)
■ const qualifier can be applied to each of pointer (i.e., top-level qualifier)
and pointee
Address
..
int i = 42; // pointee .
1000 (pointer)
// p is pointer to int i 2000
// for example: (&p)
// int* p = &i; ..
// const int* p = &i; .
// int* const p = &i;
// const int* const p = &i; 2000 42
(&i) (pointee)
..
.
■ volatile qualifier used to indicate that object can change due to agent
external to program (e.g., memory-mapped device, signal handler)
■ compiler cannot optimize away read and write operations on volatile
objects (e.g., repeated reads without intervening writes cannot be
optimized away)
■ volatile qualifier typically used when object:
2 corresponds to register of memory-mapped device
2 may be modified by signal handler (namely, object of type
volatile std::sig_atomic_t)
■ example:
volatile int x;
volatile unsigned char* deviceStatus;
■ in various contexts, auto keyword can be used as place holder for type
■ in such contexts, implication is that compiler must deduce type
■ example:
auto i = 3; // i has type int
auto j = i; // j has type int
auto& k = i; // k has type int&
const auto& n = i; // n has type const int&
auto x = 3.14; // x has type double
■ very useful in generic programming (covered later) when types not always
easy to determine
■ can potentially save typing long type names
■ can lead to more readable code (if well used)
■ if overused, can lead to bugs (sometimes very subtle ones) and difficult to
read code
inline_variable_1_1.hpp
1 inline int magic = 42;
main.cpp
1 #include <iostream>
2 #include "inline_variable_1_1.hpp"
3 int main() {
4 std::cout << magic << "\n";
5 }
other.cpp
1 #include "inline_variable_1_1.hpp"
2 void func() {/* ... */}
Arithmetic Operators
Operator Name Syntax
addition a + b Bitwise Operators
subtraction a - b
Operator Name Syntax
unary plus +a
bitwise NOT ~a
unary minus -a
bitwise AND a & b
multiplication a * b
bitwise OR a | b
division a / b
bitwise XOR a ^ b
modulo (i.e., remainder) a % b
arithmetic left shift a << b
pre-increment ++a
arithmetic right shift a >> b
post-increment a++
pre-decrement --a
post-decrement a--
Assignment and
Compound-Assignment Operators
Operator Name Syntax
assignment a = b
addition assignment a += b
subtraction assignment a -= b
multiplication assignment a *= b
division assignment a /= b
modulo assignment a %= b
bitwise AND assignment a &= b
bitwise OR assignment a |= b
bitwise XOR assignment a ^= b
arithmetic left shift assignment a <<= b
arithmetic right shift assignment a >>= b
Logical/Relational Operators
Operator Name Syntax Member and Pointer Operators
three-way comparison a <=> b
Operator Name Syntax
equal a == b
array subscript a[b]
not equal a != b
indirection *a
greater than a > b
address of &a
less than a < b
member selection a.b
greater than or equal a >= b
member selection a->b
less than or equal a <= b
member selection a.*b
logical negation !a
member selection a->*b
logical AND a && b
logical OR a || b
Other Operators
Operator Name Syntax
function call a(...)
comma a, b
ternary conditional a ? b : c
scope resolution a::b
sizeof sizeof(a)
parameter-pack sizeof sizeof...(a)
alignof alignof(T)
allocate storage new T
allocate storage (array) new T[a]
deallocate storage delete a
deallocate storage (array) delete[] a
Alternative Primary
and &&
bitor |
or ||
xor ^
compl ~
bitand &
and_eq &=
or_eq |=
xor_eq ^=
not !
not_eq !=
■ alternative tokens above probably best avoided as they lead to more
verbose code
■ An expression has a type and, if the type is not void, a value. ⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §6.9.1/9]
■ for integral operands, division operator yields algebraic quotient with any
fractional part discarded (i.e., round towards zero)
■ if quotient a / b is representable in type of result,
(a / b) * b + a % b is equal to a
■ so, assuming b is not zero and no overflow, a % b equals
a - (a / b) * b
■ result of modulus operator not necessarily nonnegative
■ example:
1 static_assert(5 % 3 == 2);
2 static_assert(5 % (-3) == 2);
3 static_assert((-5) % 3 == -2);
4 static_assert((-5) % (-3) == -2);
2 groups left-to-right
2 result true if both operands are true, and false otherwise
2 second operand is not evaluated if first operand is false (in case of built-in
logical-and operator)
■ logical-or operator (i.e., ||): [C++17 §8.15]
⁓⁓⁓⁓⁓⁓⁓
2 groups left-to-right
2 result is true if either operand is true, and false otherwise
2 second operand is not evaluated if first operand is true (in case of built-in
logical-or operator)
■ example:
int x = 0;
bool b = (x == 0 || ++x == 1);
// b equals true; x equals 0
b = (x != 0 && ++x == 1);
// b equals false; x equals 0
a
bool _result;
if (a) F
goto _true;
if (b) T b
goto _true;
if (c)
goto _true; T F
_result = false;
goto done; c
_true: T F
_result = true;
done:
True False
a
bool _result;
if (!a) T
goto _false;
if (!b) b F
goto _false;
if (!c)
goto _false; T F
_result = true;
goto done; c
_false: F
_result = false;
done: T
True False
■ looping construct
■ syntax has form:
while (expression)
statement
■ advisable to always use brace brackets, even when loop body consists of
only one statement
■ advisable to always use brace brackets, even when loop body consists of
only one statement
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 146
The do Statement: Example
Functions
■ most basic syntax for function declarations and definitions places return
type at start (i.e., leading return-type syntax)
■ basic syntax for function declaration:
return_type function_name(parameter_declarations);
1 #include <complex>
2
3 using Complex = std::complex<long double>;
4
5 // ERROR: parameter type should be reference to const
6 Complex square(Complex& z) {
7 return z * z;
8 }
9
10 int main() {
11 const Complex c1(1.0, 2.0);
12 Complex c2(1.0, 2.0);
13 Complex r1 = square(c1);
14 // must bind parameter z to argument c1
15 // Complex& z = c1;
16 // convert from const Complex to Complex&
17 // ERROR: must discard const from referee
18 Complex r2 = square(c2);
19 // must bind parameter z to argument c2
20 // Complex& z = c2;
21 // convert from Complex to Complex&
22 // OK: constness of referee unchanged
23 }
1 #include <complex>
2
3 using Complex = std::complex<long double>;
4
5 // OK: parameter type is reference to const
6 Complex square(const Complex& z) {
7 return z * z;
8 }
9
10 int main() {
11 const Complex c1(1.0, 2.0);
12 Complex c2(1.0, 2.0);
13 Complex r1 = square(c1);
14 // must bind parameter z to argument c1
15 // const Complex& z = c1;
16 // convert from const Complex to const Complex&
17 // OK: constness of referee not discarded
18 Complex r2 = square(c2);
19 // must bind parameter z to argument c2
20 // const Complex& z = c2;
21 // convert from Complex to const Complex&
22 // OK: can add const to referee
23 }
■ example:
inline bool isEven(int x) {
return x % 2 == 0;
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 171
Inlining of a Function
■ Code fragment 2:
void myFunction() {
int i = 3;
bool result = (i % 2 == 0);
}
1 #include <iostream>
2
3 constexpr double square(double x) {
4 return x * x;
5 }
6
7 int main() {
8 constexpr double a = square(2.0);
9 // must be computed at compile time
10
11 double b = square(0.5);
12 // might be computed at compile time
13
14 double t;
15 if (!(std::cin >> t)) {
16 return 1;
17 }
18 const double c = square(t);
19 // must be computed at run time
20
21 std::cout << a << ’ ’ << b << ’ ’ << c << ’\n’;
22 }
1 #include <iostream>
2
3 constexpr double power_int_helper(double x, int n) {
4 return (n > 0) ? x * power_int_helper(x, n - 1) : 1;
5 }
6
7 constexpr double power_int(double x, int n) {
8 return (n < 0) ? power_int_helper(1.0 / x, -n) :
9 power_int_helper(x, n);
10 }
11
12 int main() {
13 constexpr double a = power_int(0.5, 8);
14 // must be computed at compile time
15
16 double b = power_int(0.5, 8);
17 // might be computed at compile time
18
19 double x;
20 if (!(std::cin >> x)) {return 1;}
21 const double c = power_int(x, 2);
22 // must be computed at run time
23
24 std::cout << a << ’ ’ << b << ’ ’ << c << ’\n’;
25 }
1 #include <cassert>
2 #include <cmath>
3 #include <type_traits>
4
5 constexpr double square_root(double x) {
6 if (std::is_constant_evaluated() && x >= 0.0) {
7 double cur = 0.5 * x;
8 double old = 0.0;
9 while (cur != old) {
10 old = cur;
11 cur = 0.5 * (old + x / old);
12 }
13 return cur;
14 } else {return std::sqrt(x);}
15 }
16
17 int main() {
18 constexpr double x = 1.414213562373095;
19 constexpr double cy = x * x;
20 double y = cy;
21 constexpr double z1 = square_root(cy);
22 // uses compile-time square-root algorithm
23 double z2 = square_root(cy); // may use std::sqrt
24 double z3 = square_root(y); // uses std::sqrt
25 static_assert(std::abs(z1 - x) < 1e-6);
26 assert(std::abs(z2 - x) < 1e-6);
27 assert(std::abs(z3 - x) < 1e-6);
28 }
1 #include <type_traits>
2 #include <cassert>
3
4 constexpr int func1(int i) {
5 // LIKELY ERROR:
6 // following line of code same as "if (true) {return 42;}"
7 // due to constexpr-if, std::is_constant_evaluated() always true
8 if constexpr(std::is_constant_evaluated()) {return 42;}
9 else {return i;}
10 }
11
12 constexpr int func2(int i) {
13 if (std::is_constant_evaluated()) {return 42;}
14 else {return i;}
15 }
16
17 int main(){
18 constexpr int x = 0;
19 int y = x;
20 static_assert(func1(x) == 42);
21 static_assert(func2(x) == 42);
22 assert(func1(y) == 42); // OK, BUT LIKELY NOT INTENDED
23 assert(func2(y) == 0);
24 }
■ in some very special cases (for largely historical reasons), const variables
usable in constant expressions
■ const variable of integral or enumerated type usable in constant
expression if initializer is constant expression [C++20 §7.7/3–4]
⁓⁓⁓⁓⁓⁓⁓⁓
■ example:
1 constexpr int forty_two() {return 42;}
2 consteval bool is_even(int x) {return !(x % 2);}
3 consteval double cube(double x) {return x * x * x;}
4
5 int main() {
6 const int i = forty_two();
7 /* i is const, has integral type, and is initialized with
8 constant expression; so i usable in constant expression */
9 float x[i]; // OK
10 constexpr bool b = is_even(i); // OK
11 const double d = 42.0;
12 // d not usable in constant expression
13 // double d2 = cube(d); // ERROR: d not constexpr
14 }
adding const and/or volatile); of these, those that do not add const
and/or volatile to pointer/reference better than those that do
2 match with promotions (e.g., int to long, float to double)
■ example:
#include <cassert>
double sqrt(double x) {
assert(x >= 0);
// ...
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 190
Section 2.3.6
Input/Output (I/O)
1 #include <iostream>
2
3 int main() {
4 std::cout << "Enter an integer: ";
5 int x;
6 std::cin >> x;
7 if (std::cin) {
8 std::cout << "The integer entered was "
9 << x << ".\n";
10 } else {
11 std::cerr <<
12 "End-of-file reached or I/O error.\n";
13 }
14 }
Name Description
setw set field width
setfill set fill character
endl insert newline and flush
flush flush stream
dec use decimal
hex use hexadecimal
oct use octal
showpos show positive sign
noshowpos do not show positive sign
left left align
right right align
fixed write floating-point values in fixed-point notation
scientific write floating-point values in scientific notation
setprecision for default notation, specify maximum number of mean-
ingful digits to display before and after decimal point; for
fixed and scientific notations, specify exactly how many
digits to display after decimal point (padding with trail-
ing zeros if necessary)
1 #include <iostream>
2 #include <ios>
3 #include <iomanip>
4
5 int main() {
6 constexpr double pi = 3.1415926535;
7 constexpr double big = 123456789.0;
8 // default notation
9 std::cout << pi << ’ ’ << big << ’\n’;
10 // fixed-point notation
11 std::cout << std::fixed << pi << ’ ’ << big << ’\n’;
12 // scientific notation
13 std::cout << std::scientific << pi << ’ ’ << big << ’\n’;
14 // fixed-point notation with 7 digits after decimal point
15 std::cout << std::fixed << std::setprecision(7) << pi << ’ ’
16 << big << ’\n’;
17 // fixed-point notation with precision and width specified
18 std::cout << std::setw(8) << std::fixed << std::setprecision(2)
19 << pi << ’ ’ << std::setw(20) << big << ’\n’;
20 // fixed-point notation with precision, width, and fill specified
21 std::cout << std::setw(8) << std::setfill(’x’) << std::fixed
22 << std::setprecision(2) << pi << ’ ’ << std::setw(20) << big << ’\n’;
23 }
24
25 /* This program produces the following output:
26 3.14159 1.23457e+08
27 3.141593 123456789.000000
28 3.141593e+00 1.234568e+08
29 3.1415927 123456789.0000000
30 3.14 123456789.00
31 xxxx3.14 xxxxxxxx123456789.00
32 */
Miscellany
Name Description
noreturn function does not return
deprecated use of entity is deprecated (i.e., allowed but
discouraged)
fallthrough fall through in switch statement is deliberate
maybe_unused entity (e.g., variable) may be unused
nodiscard used to indicate that return value of function
should not be ignored
References
Classes
■ can control level of access that users of class have to its members
■ three levels of access:
1 public
2 protected
3 private
■ public: member can be accessed by any code
■ private: member can only be accessed by other members of class and
friends of class (to be discussed shortly)
■ protected: relates to inheritance (discussion deferred until later)
■ public members constitute class interface
■ private members constitute class implementation
class Widget {
// ...
};
class Widget {
private:
// ...
};
struct Widget {
// ...
};
class Widget {
public:
// ...
};
■ class example:
class Vector_2 { // Two-dimensional vector class.
public:
double x; // The x component of the vector.
double y; // The y component of the vector.
};
void func() {
Vector_2 v;
v.x = 1.0; // Set data member x to 1.0
v.y = 2.0; // Set data member y to 2.0
}
class MyInteger {
public:
// Set the value of the integer and return the old value.
int setValue(int newValue);
private:
int value;
};
inline int MyInteger::setValue(int newValue) {
int oldValue = value;
value = newValue;
return oldValue;
}
■ example:
class Point_2 { // Two-dimensional point class.
public:
using Coordinate = double; // Coordinate type.
Coordinate x; // The x coordinate of the point.
Coordinate y; // The y coordinate of the point.
};
void func() {
Point_2 p;
// ...
Point_2::Coordinate x = p.x;
// Point_2::Coordinate same as double
}
■ Suppose that we have two objects of the same type and we want to
propagate the value of one object (i.e., the source) to the other object (i.e.,
the destination).
■ This can be accomplished in one of two ways: 1) copying or 2) moving.
■ Copying propagates the value of the source object to the destination
object without modifying the source object.
■ Moving propagates the value of the source object to the destination
object and is permitted to modify the source object.
■ Moving is always at least as efficient as copying, and for many types,
moving is more efficient than copying.
■ For some types, copying does not make sense, while moving does (e.g.,
std::ostream, std::istream).
■ A copy operation does not modify the value of the source object.
■ example:
class Vector { // Two-dimensional vector class.
public:
Vector() // Default constructor.
{x_ = 0.0; y_ = 0.0;}
// ...
private:
double x_; // The x component of the vector.
double y_; // The y component of the vector.
};
Vector v; // calls Vector::Vector(); v set to (0,0)
Vector x(); // declares function x that returns Vector
■ example:
#include <string>
// class has implicitly-defined defaulted
// default constructor
struct Widget {
void foo() {}
std::string s;
};
2 move constructor
2 move assignment operator
2 copy assignment operator (if not relying on deprecated behavior)
2 destructor (if not relying on deprecated behavior)
■ example:
// class has defaulted copy constructor
class Widget {
public:
Widget(int i) {i_ = i;}
int get() const {return i_;}
private:
int i_;
};
2 copy constructor
2 copy assignment operator
2 move assignment operator
2 destructor
■ example:
// class has defaulted move constructor
struct Widget {
Widget();
void foo();
};
■ example:
class Widget {
public:
Widget(int bufferSize) { // Constructor.
// allocate some memory for buffer
bufferPtr_ = new char[bufferSize];
}
~Widget() { // Destructor.
// free memory previously allocated
delete [] bufferPtr_;
}
// copy constructor, assignment operator, ...
private:
char* bufferPtr_; // pointer to start of buffer
};
Operator Overloading
Miscellany
1 #include <iostream>
2 #include <vector>
3
4 class Sequence {
5 public:
6 Sequence(std::initializer_list<int> list) {
7 for (std::initializer_list<int>::const_iterator i =
8 list.begin(); i != list.end(); ++i)
9 elements_.push_back(*i);
10 }
11 void print() const {
12 for (std::vector<int>::const_iterator i =
13 elements_.begin(); i != elements_.end(); ++i)
14 std::cout << *i << ’\n’;
15 }
16 private:
17 std::vector<int> elements_;
18 };
19
20 int main() {
21 Sequence seq = {1, 2, 3, 4, 5, 6};
22 seq.print();
23 }
1 #include <cstdlib>
2
3 // one-dimensional integer array class
4 class IntArray {
5 public:
6 // create array of int with size elements
7 explicit IntArray(std::size_t size) { /* ... */ };
8 // ...
9 };
10
11 void processArray(const IntArray& x) {
12 // ...
13 }
14
15 int main() {
16 IntArray a = 42; // ERROR: cannot convert
17 processArray(42); // ERROR: cannot convert
18 }
■ in above code example, we want to have const and non-const overloads of get
member function that can each be used in constant expressions
■ so both overloads of get need to be constexpr
■ if constexpr member functions were implicitly const, it would be impossible to
overload on const in manner we wish to do here, since second overload of get
would automatically become const member function (resulting in multiple
conflicting definitions of const member function get)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 290
The mutable Qualifier
of Gadget class that takes single int parameter and has return type of
float
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 293
Pointers to Members (Continued)
■ since pointer to member is not associated with any class object instance,
dereferencing pointer to member requires object (or pointer to object) to
be specified
■ given object x of type T, can access member through pointer to member
ptm by applying member-selection operator .* to x using expression
x.*ptm
■ given pointer p to object of type T, can access member through pointer to
member ptm by applying member-selection operator ->* to p using
expression p->*ptm
1 #include <iostream>
2 #include <iterator>
3
4 struct Point {double x; double y;};
5 struct Thing {int i; float f;};
6
7 template <auto P, class Iter, class T>
8 T accumulate(Iter first, Iter last, T init_sum) {
9 for (auto i = first; i != last; ++i)
10 {init_sum += i->*P;}
11 return init_sum;
12 }
13
14 int main() {
15 constexpr Point p[]{{1.0, 21.0}, {0.5, 21.0}, {0.5, 0.0}};
16 constexpr Thing t[]{{1, 0.1f}, {2, 0.1f}, {3, 0.1f}};
17 std::cout
18 << accumulate<&Point::x>(std::begin(p), std::end(p), 0.0) << ’ ’
19 << accumulate<&Point::y>(std::begin(p), std::end(p), 0.0) << ’\n’;
20 std::cout
21 << accumulate<&Thing::i>(std::begin(t), std::end(t), 0) << ’ ’
22 << accumulate<&Thing::f>(std::begin(t), std::end(t), 0.0f) << ’\n’;
23 }
■ inserter and extractor should use compatible formats (i.e., what is written
by inserter should be readable by extractor)
1 #include <tuple>
2 #include <array>
3 #include <cassert>
4
5 int main() {
6 int a[3] = {1, 2, 3};
7 auto [a0, a1, a2] = a;
8 assert(a0 == a[0] && a1 == a[1] && a2 == a[2]);
9
10 int b[3] = {0, 2, 3};
11 auto& [b0, b1, b2] = b;
12 ++b0;
13 assert(b[0] == 1);
14
15 std::array<int, 3> c = {1, 2, 3};
16 auto [c0, c1, c2] = c;
17 assert(c0 == c[0] && c1 == c[1] && c2 == c[2]);
18
19 auto t = std::tuple(true, 42, ’A’);
20 auto [tb, ti, tc] = t;
21 assert(tb == true && ti == 42 && tc == ’A’);
22 }
1 #include <map>
2 #include <string>
3 #include <iostream>
4
5 int main() {
6 std::map<std::string, int> m = {
7 {"apple", 1},
8 {"banana", 2},
9 {"orange", 3},
10 };
11 for (auto&& [key, value] : m) {
12 std::cout << key << ’ ’ << value << ’\n’;
13 }
14 }
1 // literal type
2 class Widget {
3 public:
4 constexpr Widget(int i = 0) : i_(i) {}
5 ~Widget() = default; // constexpr destructor
6 private:
7 int i_;
8 };
9
10 // not literal type
11 class Gadget {
12 public:
13 constexpr Gadget() {}
14 ~Gadget() {} // non-constexpr destructor
15 };
16
17 // not literal type
18 // no constexpr constructor, excluding copy/move constructor
19 class Foo {
20 public:
21 Foo() {};
22 ~Foo() = default; // constexpr destructor
23 };
1 #include <set>
2 #include <vector>
3
4 constexpr std::set s{1, 2, 3};
5 // ERROR: not literal type
6
7 constexpr int i;
8 // ERROR: not initialized
9
10 float func();
11 constexpr float f = func();
12 // ERROR: initializer is not constant expression since func is not
13 // constexpr function
14
15 constexpr std::vector<int> v{1, 2, 3};
16 // ERROR: construction result is not constant expression since
17 // constructor of std::vector<int> allocates memory via new
[C++20 §9.2.5/6]
⁓⁓⁓⁓⁓⁓⁓⁓
2 must not be a coroutine
2 its return type (if any) must be literal type
2 each of its parameters must be of literal type
2 there exists at least one set of argument values such that invocation of
function could be evaluated expression of core constant expression
2 function body must be either deleted or defaulted or contain any statements
except:
2 goto statement
2 statement with label other than case and default
2 definition of variable of non-literal type
2 definition of variable of static or thread storage duration
1 #include <set>
2 #include <iostream>
3
4 // ERROR: return type not literal type
5 constexpr std::set<int> get_values()
6 {return std::set<int>{1, 2, 3};}
7
8 // ERROR: parameter type not literal type
9 constexpr void foo(std::set<int> s) { /* ... */ }
10
11 // ERROR: no argument exists such that function can be used
12 // in constant expression
13 constexpr void output(int i) {std::cout << i << ’\n’;}
14
15 constexpr void func() {
16 std::set<int> v{1, 2, 3};
17 // ERROR: definition of variable of non-literal type
18 // ...
19 }
20
21 constexpr int count() {
22 static unsigned int i = 0;
23 // ERROR: definition of variable with static storage
24 // duration
25 return i++;
26 }
1 #include <set>
2
3 class Widget {
4 public:
5 constexpr Widget(std::set<int> s) : i_(s.size()) {}
6 // ERROR: type of constructor parameter not literal type
7 // ...
8 private:
9 // only one data member
10 int i_;
11 };
12
13 // OK
14 class Base {
15 public:
16 Base(int i) : i_(i) {}
17 private:
18 int i_;
19 };
20
21 class Derived : public Base {
22 public:
23 constexpr Derived() : Base(42) {}
24 // ERROR: Base constructor not constexpr
25 // ...
26 };
1 #include <utility>
2
3 class widget {
4 public:
5 constexpr widget(int i = 0) : ip_{new int(i)} {}
6 constexpr ~widget() {delete ip_;}
7 constexpr widget(widget&& other) : ip_(nullptr)
8 {std::swap(ip_, other.ip_);}
9 constexpr widget& operator=(widget&& other) {
10 std::swap(ip_, other.ip_);
11 return *this;
12 }
13 constexpr int get() const {return *ip_;}
14 private:
15 int *ip_;
16 };
17
18 constexpr int func() {
19 widget w(42);
20 widget u(std::move(w));
21 w = std::move(u);
22 return w.get();
23 }
24
25 static_assert(func() == 42);
1 #include <stdexcept>
2 #include <cassert>
3
4 constexpr double sqrt(double x) {
5 // if assertion fails, sqrt function will not yield
6 // constant expression
7 assert(x >= 0.0);
8 double result = 0.0;
9 // ... (correctly initialize result)
10 return result;
11 }
12
13 constexpr int foo(unsigned x) {
14 unsigned i = 0;
15 // ... (code that changes i)
16 // assume odd i indicative of bug
17 // if i is odd (which would result in exception
18 // being thrown), foo function will not yield
19 // constant expression
20 if (i & 1) {throw std::logic_error("i is odd");}
21 return 0;
22 }
1 #include <cassert>
2 #include <iostream>
3 #include "BoolVector.hpp"
4
5 int main() {
6 constexpr int bits[] = {0, 0, 1, 1, 0, 1, 0, 1};
7 constexpr int n = sizeof(bits) / sizeof(int);
8 BoolVector v(n);
9 BoolVector w(n);
10 assert(v.size() == n && w.size() == n);
11 for (int i = 0; i < n; ++i) {
12 w[i] = v[i] = bits[i];
13 }
14 const BoolVector& cv = v;
15 for (int i = 0; i < n; ++i) {
16 assert(v[i] == bits[i]);
17 assert(w[i] == bits[i]);
18 assert(cv[i] == bits[i]);
19 std::cout << (v[i] ? ’1’ : ’0’);
20 }
21 std::cout << ’\n’;
22 }
Functors
2 greaterEqual(x, y) = ¬less(x, y)
■ note: “¬” denotes logical NOT, “∧” denotes logical AND, and “∨” denotes
logical OR
1 #include <algorithm>
2 #include <cassert>
3 #include <forward_list>
4 #include <functional>
5 #include <iterator>
6
7 template <std::forward_iterator I, class Compare>
8 void bubble_sort(I first, I last, Compare less) {
9 for (auto sorted = first; first != last; last = sorted) {
10 sorted = first;
11 for (auto cur = first, prev = first; ++cur != last; ++prev) {
12 if (less(*cur, *prev)) {
13 std::iter_swap(cur, prev);
14 sorted = cur;
15 }
16 }
17 }
18 }
19
20 int main() {
21 std::forward_list<int> values{7, 0, 6, 1, 5, 2, 4, 3};
22 bubble_sort(values.begin(), values.end(), std::less<int>());
23 assert((values == std::forward_list<int>{0, 1, 2, 3, 4, 5, 6, 7}));
24 bubble_sort(values.begin(), values.end(), std::greater<int>());
25 assert((values == std::forward_list<int>{7, 6, 5, 4, 3, 2, 1, 0}));
26 }
References
Templates
Function Templates
■ each of above functions has same general form; that is, for some type T,
we have:
T max(T x, T y)
{return x > y ? x : y;}
■ would be nice if we did not have to repeatedly type, debug, test, and
maintain nearly identical code
■ in effect, would like code to be parameterized on type T
Class Templates
■ again, would be nice if we did not have to repeatedly type, debug, test,
and maintain nearly identical code
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 353
Class Templates
■ class template is family of classes parameterized on one or more
parameters
■ each template parameter can be: non-type (e.g., integral constant), type,
template, or parameter pack (in case of variadic template)
■ syntax has general form:
template <parameter_list> class_declaration
■ parameter_list: parameter list for class
■ class_declaration: class/struct declaration or definition
■ example:
// declaration of class template
template <class T, unsigned int size>
class MyArray;
// definition of class template
template <class T, unsigned int size>
class MyArray {
// ...
T array_[size];
};
MyArray<double, 100> x;
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 354
Class Templates (Continued)
1 #include <vector>
2 #include <list>
3 #include <deque>
4 #include <memory>
5
6 template <template <class, class> class Container, class Value>
7 class Stack {
8 public:
9 // ...
10 private:
11 Container<Value, std::allocator<Value>> data_;
12 };
13
14 int main() {
15 Stack<std::vector, int> s1;
16 Stack<std::list, int> s2;
17 Stack<std::deque, int> s3;
18 }
1 #include <vector>
2 #include <tuple>
3 #include <set>
4 #include <string>
5
6 using namespace std::string_literals;
7
8 auto get_tuple() {
9 return std::tuple("Zaphod"s, 42);
10 // deduces tuple<std::string, int>
11 }
12
13 int main() {
14 std::vector v{1, 2, 3};
15 // deduces vector<int>
16 std::tuple t(true, ’A’, 42);
17 // deduces tuple<bool, char, int>
18 std::pair p(42, "Hello"s);
19 // deduces pair<int, std::string>
20 std::set s{0.5, 0.25};
21 // deduces set<double>
22 //auto ptr = new std::tuple(true, 42);
23 // should deduce tuple<bool, int>?
24 // fails to compile with GCC 7.1.0
25 }
1 #include <cstdlib>
2 #include <iostream>
3
4 template<class T, T v>
5 struct integral_constant {
6 using value_type = T;
7 static constexpr value_type value = v;
8 using type = integral_constant;
9 constexpr operator value_type() const noexcept
10 {return value;}
11 constexpr value_type operator()() const noexcept
12 {return value;}
13 };
14
15 using forty_two_type = integral_constant<int, 42>;
16
17 int main() {
18 constexpr forty_two_type x;
19 constexpr auto v = x.value;
20 std::cout << v << ’\n’;
21 }
1 #include <cstdlib>
2 #include <iostream>
3
4 template<auto v>
5 struct integral_constant {
6 using value_type = decltype(v);
7 static constexpr value_type value = v;
8 using type = integral_constant;
9 constexpr operator value_type() const noexcept
10 {return value;}
11 constexpr value_type operator()() const noexcept
12 {return value;}
13 };
14
15 using forty_two_type = integral_constant<42>;
16
17 int main() {
18 constexpr forty_two_type x;
19 constexpr auto v = x.value;
20 std::cout << v << ’\n’;
21 }
Variable Templates
1 #include <limits>
2 #include <complex>
3 #include <iostream>
4
5 template <typename T>
6 constexpr T pi =
7 T(3.14159265358979323846264338327950288419716939937510L);
8
9 int main() {
10 std::cout.precision(
11 std::numeric_limits<long double>::max_digits10);
12 std::cout
13 << pi<int> << ’\n’
14 << pi<float> << ’\n’
15 << pi<double> << ’\n’
16 << pi<long double> << ’\n’
17 << pi<std::complex<float>> << ’\n’
18 << pi<std::complex<double>> << ’\n’
19 << pi<std::complex<long double>> << ’\n’;
20 }
Alias Templates
1 #include <iostream>
2 #include <set>
3
4 // alias template for set that employs std::greater for
5 // comparison
6 template <typename Value,
7 typename Alloc = std::allocator<Value>>
8 using GreaterSet = std::set<Value,
9 std::greater<Value>, Alloc>;
10
11 int main() {
12 std::set x{1, 4, 3, 2};
13 GreaterSet<int> y{1, 4, 3, 2};
14 for (auto i : x) {
15 std::cout << i << ’\n’;
16 }
17 std::cout << ’\n’;
18 for (auto i : y) {
19 std::cout << i << ’\n’;
20 }
21 }
Variadic Templates
■ example:
#include <cassert>
template <typename... Ts>
int number_of_arguments(const Ts&... args) {
return sizeof...(args);
}
int main() {
assert(number_of_arguments(1, 2, 3) == 3);
assert(number_of_arguments() == 0);
}
1 #include <iostream>
2 #include <string>
3
4 using namespace std::string_literals;
5
6 template <class T>
7 auto sum(T x) {
8 return x;
9 }
10
11 template <class T, class... Args>
12 auto sum(T x, Args... args) {
13 return x + sum(args...);
14 }
15
16 int main() {
17 auto x = sum(42.5, -1.0, 0.5f);
18 auto y = sum("The "s, "answer "s, "is "s);
19 std::cout << y << x << ".\n";
20 // sum(); // ERROR: no matching function call
21 }
22
23 /* Output:
24 The answer is 42.
25 */
1 #include <iostream>
2
3 template <int... Args>
4 constexpr int int_array[] = {Args...};
5
6 int main() {
7 for (auto i : int_array<1,2,4,8>) {
8 std::cout << i << ’\n’;
9 }
10 }
11
12 /* Output:
13 1
14 2
15 4
16 8
17 */
1 #include <iostream>
2 #include <string>
3 #include <tuple>
4
5 template <class... Ts>
6 using My_tuple = std::tuple<bool, Ts...>;
7
8 int main() {
9 My_tuple<int, std::string> t(true, 42,
10 "meaning of life");
11 std::cout << std::get<0>(t) << ’ ’
12 << std::get<1>(t) << ’ ’
13 << std::get<2>(t) << ’\n’;
14 }
15
16 /* Output:
17 1 42 meaning of life
18 */
1 #include <iostream>
2 #include <string>
3
4 using namespace std::string_literals;
5
6 template <class T>
7 auto sum(T x) {
8 return x;
9 }
10
11 template <class T, class... Args>
12 auto sum(T x, Args... args) {
13 return x + sum(args...);
14 }
15
16 int main() {
17 auto x = sum(42.5, -1.0, 0.5f);
18 auto y = sum("The "s, "answer "s, "is "s);
19 std::cout << y << x << ".\n";
20 // sum(); // ERROR: no matching function call
21 }
22
23 /* Output:
24 The answer is 42.
25 */
1 #include <iostream>
2 #include <string>
3
4 using namespace std::string_literals;
5
6 template <class T, class... Args>
7 auto sum(T x, Args... args) {
8 return x + (... + args);
9 }
10
11 int main() {
12 auto x = sum(42.5, -1.0, 0.5f);
13 auto y = sum("The "s, "answer "s, "is "s);
14 std::cout << y << x << ".\n";
15 // sum(); // ERROR: no matching function call
16 }
17
18 /* Output:
19 The answer is 42.
20 */
1 #include <iostream>
2 #include <string>
3
4 using namespace std::string_literals;
5
6 template <class... Args>
7 std::ostream& print(const Args&... args) {
8 return (std::cout << ... << args);
9 }
10
11 int main() {
12 print("The "s, "answer "s, "is "s, 42, ".\n"s);
13 print(); // OK: no-op
14 }
15
16 /* Output:
17 The answer is 42.
18 */
1 #include <iostream>
2 #include <tuple>
3
4 // heterogeneous list of constant values
5 template <auto... vs> class value_list {
6 public:
7 constexpr value_list() : v_(vs...) {}
8 template <int n> constexpr auto get() const
9 {return std::get<n>(v_);}
10 constexpr int size() const {return sizeof...(vs);}
11 private:
12 std::tuple<decltype(vs)...> v_;
13 };
14
15 int main() {
16 constexpr value_list<42, true, ’A’> v;
17 constexpr auto n = v.size();
18 constexpr auto a = v.get<0>();
19 constexpr auto b = v.get<1>();
20 constexpr auto c = v.get<2>();
21 std::cout << n << ’ ’ << a << ’ ’ << b << ’ ’ << c << ’\n’;
22 }
1 #include <iostream>
2 #include <tuple>
3
4 // homogeneous list of constant values
5 template <auto v1, decltype(v1)... vs> class value_list {
6 public:
7 constexpr value_list() : v_(v1, vs...) {}
8 template <int n> constexpr auto get() const
9 {return std::get<n>(v_);}
10 constexpr int size() const {return 1 + sizeof...(vs);}
11 private:
12 std::tuple<decltype(v1), decltype(vs)...> v_;
13 };
14
15 int main() {
16 constexpr value_list<1, 2, 3> v;
17 constexpr auto n = v.size();
18 constexpr auto a = v.get<0>();
19 constexpr auto b = v.get<1>();
20 constexpr auto c = v.get<2>();
21 std::cout << n << ’ ’ << a << ’ ’ << b << ’ ’ << c << ’\n’;
22 }
Template Specialization
1 #include <iostream>
2
3 // unspecialized version
4 template <typename T, typename V>
5 struct Widget {
6 Widget() {std::cout << "unspecialized\n";}
7 };
8
9 // partial specialization
10 template <typename T>
11 struct Widget<int, T> {
12 Widget() {std::cout << "partial\n";}
13 };
14
15 // explicit specialization
16 template <>
17 struct Widget<int, int> {
18 Widget() {std::cout << "explicit\n";}
19 };
20
21 int main() {
22 Widget<double, int> w1; // unspecialized version
23 Widget<int, double> w2; // partial specialization
24 Widget<int, int> w3; // explicit specialization
25 }
1 #include <limits>
2
3 // unspecialized version
4 template <int X, int Y>
5 constexpr int quotient = X / Y;
6
7 // partial specialization (which prevents division by zero)
8 template <int X>
9 constexpr int quotient<X, 0> = (X < 0) ?
10 std::numeric_limits<int>::min() : std::numeric_limits<int>::max();
11
12 static_assert(quotient<4, 2> == 2);
13 static_assert(quotient<5, 3> == 1);
14 static_assert(quotient<4, 0> == std::numeric_limits<int>::max());
15 static_assert(quotient<-4, 0> == std::numeric_limits<int>::min());
16
17 int main() {}
Miscellany
References
Concepts
1 #include <algorithm>
2 #include <concepts>
3 #include <iterator>
4 #include <utility>
5
6 template<std::random_access_iterator Iter, class Comp = std::less<>>
7 requires std::indirect_binary_predicate<Comp, Iter, Iter>
8 void shell_sort(Iter begin, Iter end, Comp less = {}) {
9 for (auto gap = (end - begin) / 2; gap != 0; gap /= 2) {
10 for (auto i = begin + gap; i != end; ++i) {
11 for (auto j = i; j >= begin + gap && less(*j, j[-gap]);
12 j -= gap) {std::iter_swap(j - gap, j);}
13 }
14 }
15 }
1 #include <iostream>
2 #include <list>
3 #include "shell_sort_concepts.hpp"
4
5 int main() {
6 std::list<int> v{1, 7, 2, 6, 3, 5, 4, 0};
7 shell_sort(v.begin(), v.end(), std::greater<>());
8 for (auto&& i : v) {std::cout << i << ’\n’;}
9 }
Basics of Concepts
■ in most contexts where auto keyword can be used, this keyword can be
prefixed by concept name in order to constrain deduced type
■ if deduced type does not satisfy constraint, compiler error results
■ example:
int func1();
float func2();
std::integral auto x = func1();
// OK: deduced type is int, which
// satisfies constraint std::integral
std::integral auto y = func2();
// ERROR: deduced type is float, which
// violates constraint std::integral
1 #include <concepts>
2 #include <iostream>
3
4 constexpr bool is_power_of_two(std::size_t n) {
5 int count = 0;
6 for (; n; n >>= 1) {
7 if (n & 1) {++count;}
8 }
9 return count == 1;
10 }
11
12 template<class T, std::size_t N>
13 requires std::floating_point<T> && (is_power_of_two(N))
14 void func(T (&a)[N]) {
15 std::cout << N << ’ ’ << sizeof(T) << ’\n’;
16 // ... (algorithm that requires power-of-two array size)
17 }
18
19 int main() {
20 float a[1024];
21 func(a); // OK: array size is power of 2
22 float b[1023];
23 // func(b); // ERROR: array size is not power of 2
24 }
1 #include <array>
2 #include <concepts>
3 #include <cstddef>
4
5 constexpr bool is_divisible(std::size_t m, std::size_t n)
6 {return !(m % n);}
7
8 template<std::integral T, std::size_t N>
9 requires (is_divisible(N, 4))
10 class Widget {
11 public:
12 // ...
13 private:
14 std::array<T, N> data_;
15 };
16
17 int main() {
18 Widget<int, 16> a; // OK: 16 is multiple of 4
19 // Widget<int, 15> b; // ERROR: 15 not multiple of 4
20 // Widget<float, 16> c; // ERROR: float not integral type
21 }
1 #include <iostream>
2
3 template<bool enable_foo>
4 class Widget {
5 public:
6 void foo() requires (enable_foo) {std::cout << "foo\n";}
7 void bar() {std::cout << "bar\n";}
8 // ...
9 };
10
11 int main() {
12 Widget<true> a;
13 a.foo(); // OK: has foo member
14 a.bar();
15 Widget<false> b;
16 // b.foo(); // ERROR: no foo member
17 b.bar();
18 }
1 #include <concepts>
2 #include <iostream>
3
4 constexpr bool is_power_of_two(std::size_t n) {
5 for (; n; n >>= 1) {
6 if (n & 1) {return !(n >> 1);}
7 }
8 return false;
9 }
10
11 template<std::size_t N> concept power_of_two =
12 is_power_of_two(N);
13
14 // constrained generic lambda
15 auto f = []<std::floating_point T, std::size_t N>(T (&a)[N])
16 requires power_of_two<N> {
17 std::cout << N << ’\n’;
18 };
19
20 int main() {
21 float x[4] = {1, 2, 3, 4};
22 float y[3] = {1, 2, 3};
23 f(x); // OK: array size is power of 2
24 // f(y); // ERROR: no matching function call
25 }
1 #include <concepts>
2 #include <type_traits>
3
4 struct widget {using type = int;};
5 struct gadget {using type = float;};
6 struct doodad {};
7
8 template<class T>
9 constexpr bool func1(T x) {return false;}
10 template<class T>
11 requires std::integral<typename T::type>
12 constexpr bool func1(T x) {return true;}
13
14 // func2 is not functionally equivalent to func1
15 template<class T>
16 constexpr bool func2(T x) {return true;}
17 template<class T>
18 requires (!std::integral<typename T::type>)
19 constexpr bool func2(T x) {return false;}
20
21 static_assert(func1(widget{}) == func2(widget{}));
22 static_assert(func1(gadget{}) == func2(gadget{}));
23 static_assert(func1(doodad{}) != func2(doodad{})); // why not equal?
24 static_assert(func1(42) != func2(42)); // why not equal?
1 #include <concepts>
2 #include <iostream>
3
4 template<class T>
5 concept stream_insertable = requires(T x, std::ostream& out) {
6 {out << x} -> std::same_as<std::ostream&>;
7 };
8
9 template<stream_insertable... Ts>
10 std::ostream& print(std::ostream& out, Ts... args) {
11 return (out << ... << args);
12 }
13
14 struct Widget {};
15
16 int main() {
17 print(std::cout, "hello", ’ ’, 42, ’ ’, 42.42f, ’\n’);
18 //print(std::cout, Widget{});
19 // ERROR: template constraint violation (stream_insertable)
20 }
1 #include <concepts>
2
3 struct widget {
4 int foo() {return 42;}
5 };
6
7 template<class T>
8 requires requires(T x) {
9 {x.foo()} -> std::same_as<int>;
10 }
11 int func(T x) {return x.foo() + 1;}
12
13 int main() {
14 widget w;
15 func(w);
16 // func(42); // ERROR: constraint violation
17 }
1 #include <array>
2
3 template<class T>
4 concept int_sized = requires {
5 sizeof(T) == sizeof(int); // BUG: missing requires keyword
6 };
7
8 static_assert(int_sized<char> && sizeof(char) == 1);
9 // OK: YIKES!
10 static_assert(int_sized<char[4096]> &&
11 sizeof(char[4096]) == 4096);
12 // OK: YIKES!
13 static_assert(int_sized<std::array<int, 4096>> &&
14 sizeof(std::array<int, 4096>) == 4096 * sizeof(int));
15 // OK: YIKES!
16
17 static_assert(!int_sized<void>);
18 // OK: YIKES! void has no size
1 #include <concepts>
2 #include <utility>
3
4 template<std::movable T>
5 void smart_swap(T& x, T& y) {
6 constexpr bool has_member_swap = requires(T x, T y) {
7 x.swap(y);
8 };
9 constexpr bool has_nonmember_swap = requires(T x, T y) {
10 swap(x, y);
11 };
12 if constexpr(has_member_swap) {
13 // use member swap
14 x.swap(y);
15 } else if constexpr(has_nonmember_swap) {
16 // use nonmember swap
17 swap(x, y);
18 } else {
19 // use general swap algorithm
20 T tmp = std::move(x);
21 x = std::move(y);
22 y = std::move(tmp);
23 }
24 }
1 #include "smart_swap.hpp"
2 #include <iostream>
3
4 namespace foo {
5 struct widget {
6 void swap(widget& other) {std::cout << "widget member swap\n";}
7 };
8 void swap(widget& x, widget& y)
9 {std::cout << "widget nonmember swap\n";}
10 struct gadget {};
11 void swap(gadget& x, gadget& y)
12 {std::cout << "gadget nonmember swap\n";}
13 struct doodad {};
14 }
15
16 int main() {
17 foo::widget w1, w2;
18 foo::gadget g1, g2;
19 foo::doodad d1, d2;
20 smart_swap(w1, w2); // uses member swap
21 smart_swap(g1, g2); // uses nonmember swap
22 smart_swap(d1, d2); // uses general swap algorithm
23 }
1 #include <array>
2 #include <cassert>
3 #include <concepts>
4 #include <iterator>
5 #include <list>
6 #include <sstream>
7
8 template<std::input_iterator I, class T>
9 requires requires(I i, T t) {
10 {t += *i} -> std::same_as<T&>;
11 }
12 constexpr T accumulate(I first, I last, T init) {
13 for (; first != last; ++first) {init += *first;}
14 return init;
15 }
16
17 int main() {
18 constexpr std::array<int, 4> a{1, 2, 3};
19 static_assert(accumulate(a.begin(), a.end(), 0) == 6);
20 const std::list<int> b{1, 2, 3};
21 assert(accumulate(b.begin(), b.end(), 0) == 6);
22 std::stringstream ss("1.0 0.5 0.5");
23 assert(accumulate(std::istream_iterator<float>(ss),
24 std::istream_iterator<float>(), 0.0f) == 2.0);
25 }
1 #include <type_traits>
2
3 template<class T>
4 requires std::is_integral_v<T>
5 constexpr int func1(T x) {return 0;}
6
7 template<class T>
8 requires std::is_integral_v<T> && std::is_unsigned_v<T>
9 constexpr int func1(T x) {return 1;}
10
11 template<class T> concept integral = std::is_integral_v<T>;
12 template<class T> concept notsigned = std::is_unsigned_v<T>;
13
14 template<class T>
15 requires integral<T>
16 constexpr int func2(T x) {return 0;}
17
18 template<class T>
19 requires integral<T> && notsigned<T>
20 constexpr int func2(T x) {return 1;}
21
22 static_assert(func1(42) == 0);
23 // static_assert(func1(42U) == 1); // ERROR: ambiguous function call
24 static_assert(func2(42) == 0);
25 static_assert(func2(42U) == 1);
1 #include <cassert>
2 #include <concepts>
3 #include <string>
4
5 // unconstrained primary template
6 template<class T> struct widget {
7 static std::string value() {return "primary";}
8 };
9
10 // partial specialization
11 template<std::integral T> struct widget<T> {
12 static std::string value() {return "integral";}
13 };
14
15 // explicit specialization
16 template<> struct widget<bool> {
17 static std::string value() {return "explicit";}
18 };
19
20 int main() {
21 assert(widget<double>::value() == "primary");
22 assert(widget<int>::value() == "integral");
23 assert(widget<bool>::value() == "explicit");
24 }
1 #include <cassert>
2 #include <complex>
3 #include <optional>
4 #include <string>
5 #include <type_traits>
6
7 template<class T>
8 requires std::destructible<T>
9 const auto forty_two = std::optional<T>();
10
11 template<class T>
12 requires std::destructible<T> &&
13 std::constructible_from<std::optional<T>, std::in_place_t, int>
14 const auto forty_two<T> = std::optional<T>(std::in_place_t{}, T(42));
15
16 int main() {
17 auto s = forty_two<std::string>;
18 assert(!s.has_value());
19 auto cf = forty_two<std::complex<float>>;
20 assert(cf.has_value() && *cf == 42.0f);
21 auto i = forty_two<int>;
22 assert(i.has_value() && *i == 42);
23 auto f = forty_two<float>;
24 assert(f.has_value() && *f == 42.0f);
25 }
Constraint Ordering
std::is_floating_point_v<T>)
Additional Examples
Standard-Library Concepts
1 #include <ranges>
2 #include <vector>
3 #include <list>
4 #include <forward_list>
5 #include <string>
6
7 static_assert(
8 std::ranges::range<std::string> &&
9 std::ranges::range<std::vector<int>> &&
10 std::ranges::sized_range<std::list<int>> &&
11 !std::ranges::view<std::vector<int>> &&
12 std::ranges::forward_range<std::forward_list<int>> &&
13 std::ranges::bidirectional_range<std::list<int>> &&
14 std::ranges::random_access_range<std::vector<int>> &&
15 std::ranges::contiguous_range<std::vector<int>> &&
16 !std::ranges::contiguous_range<std::list<int>> &&
17 std::ranges::common_range<std::vector<int>>
18 );
References
1 Mateusz Pusz. C++ Concepts and Ranges. Meeting C++, 2018. Available
online at https://youtu.be/pe05ZWdh0N0.
2 Saar Raz. C++20 Concepts: A Day in the Life. CppCon, 2019. Available
online at https://youtu.be/qawSiMIXtE4.
Lambda Expressions
1 #include <iostream>
2
3 int main() {
4 []{std::cout << "Hello, World!\n";}();
5 }
1 #include <iostream>
2
3 struct Hello {
4 void operator()() const {
5 std::cout << "Hello, World!\n";
6 }
7 };
8
9 int main() {
10 Hello hello;
11 hello();
12
Copyright}© 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 497
Linear-Function Functor Example
1 #include <iostream>
2
3 auto make_linear_func(float a, float b)
4 {return [a, b](float x){return a * x + b;};}
5
6 int main() {
7 float a = 0.5f; float b = 1.0f;
8 auto f = make_linear_func(a, b);
9 std::cout << f(1.0f) << ’\n’;
10 }
1 #include <iostream>
2
3 class linear_func {
4 public:
5 linear_func(float a, float b) : a_(a), b_(b) {}
6 float operator()(float x) const {return a_ * x + b_;}
7 private:
8 float a_; float b_;
9 };
10
11 linear_func make_linear_func(float a, float b)
12 {return linear_func(a, b);}
13
14 int main() {
15 float a = 0.5f; float b = 1.0f;
16 linear_func f = make_linear_func(a, b);
17 std::cout << f(1.0f) << ’\n’;
18 }
1 #include <iostream>
2 #include <algorithm>
3 #include <cstdlib>
4 #include <vector>
5
6 struct abs_less {
7 bool operator()(int x, int y) const
8 {return std::abs(x) < std::abs(y);}
9 };
10
11 int main() {
12 std::vector<int> v{-3, 3, 4, 0, -2, -1, 2, 1, -4};
13 std::sort(v.begin(), v.end(), abs_less());
14 for (auto x : v) std::cout << x << ’\n’;
15 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 499
Capturing Objects
1 #include <iostream>
2 #include <vector>
3 #include <algorithm>
4
5 class mod {
6 public:
7 mod(int m_) : m(m_) {}
8 int operator()(int x) const {return x % m;}
9 private:
10 int m;
11 };
12
13 int main() {
14 int m = 2;
15 std::vector<int> v{0, 1, 2, 3};
16 std::transform(v.begin(), v.end(), v.begin(), mod(m));
17 for (auto x : v) std::cout << x << ’\n’;
18 }
1 #include <iostream>
2 #include <vector>
3 #include <algorithm>
4
5 int main() {
6 int m = 2;
7 std::vector<int> v{0, 1, 2, 3};
8 std::transform(v.begin(), v.end(), v.begin(),
9 [m](int x){return x % m;});
10 for (auto x : v) std::cout << x << ’\n’;
11 }
■ m captured by value
■ approximately 0.5 lines of code to generate functor
1 #include <iostream>
2 #include <vector>
3 #include <algorithm>
4
5 class cum_prod {
6 public:
7 cum_prod(int& prod_) : prod(prod_) {}
8 void operator()(int x) const {prod *= x;}
9 private:
10 int& prod;
11 };
12
13 int main() {
14 std::vector<int> v{2, 3, 4};
15 int prod = 1;
16 std::for_each(v.begin(), v.end(), cum_prod(prod));
17 std::cout << prod << ’\n’;
18 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 506
Product Example: Without Lambda Expression
1 #include <iostream>
2 #include <vector>
3 #include <algorithm>
4
5 class cum_prod {
6 public:
7 cum_prod(int& prod_) : prod(prod_) {}
8 void operator()(int x) const {prod *= x;}
9 private:
10 int& prod;
11 };
12
13 int main() {
14 std::vector<int> v{2, 3, 4};
15 int prod = 1;
16 std::for_each(v.begin(), v.end(), cum_prod(prod));
17 std::cout << prod << ’\n’;
18 }
1 #include <iostream>
2 #include <vector>
3 #include <algorithm>
4
5 int main() {
6 std::vector<int> v{2, 3, 4};
7 int prod = 1;
8 std::for_each(v.begin(), v.end(),
9 [&prod](int x)->void{prod *= x;});
10 std::cout << prod << ’\n’;
11 }
double a = 2.14;
double b = 3.14;
double c = 42.0;
// capture all objects by reference (i.e., a, b, and c)
[&](double x, double y){return a * x + b * y + c;}
// capture all objects by value (i.e., a, b, and c)
[=](double x, double y){return a * x + b * y + c;}
// capture all objects by value, except a
// which is captured by reference
[=,&a](double x, double y){return a * x + b * y + c;}
// capture all objects by reference, except a
// which is captured by value
[&,a](double x, double y){return a * x + b * y + c;}
1 #include <iostream>
2
3 int main() {
4 int x = 0;
5 int y = 1;
6 auto f = [&count = x, inc = y + 1](){
7 return count += inc;
8 };
9 std::cout << f() << ’ ’;
10 std::cout << f() << ’\n’;
11 }
12
13 // output: 2 4
1 #include <iostream>
2 #include <complex>
3 #include <string>
4
5 struct Add {
6 template <class T, class U>
7 auto operator()(T x, U y) {return x + y;};
8 };
9
10 int main() {
11 using namespace std::literals;
12 Add add;
13 std::cout << add(1, 2) << ’ ’ << add(1.0, 2.0) << ’ ’
14 << add(1.0, 2.0i) << ’ ’ << add("Jell"s, "o"s) << ’\n’;
15 }
1 #include <iostream>
2 #include <vector>
3 #include <algorithm>
4
5 int main() {
6 std::vector<int> v{0, 1, 2, 3, 4, 5, 6, 7};
7 // sort elements of vector in descending order
8 std::sort(v.begin(), v.end(),
9 [](auto i, auto j) {return i > j;});
10 std::for_each(v.begin(), v.end(),
11 [](auto i) {std::cout << i << ’\n’;});
12 }
1 int main() {
2 // deduced types for x and y are independent
3 auto f = [](auto x, auto y) {
4 // ...
5 };
6 // x and y must have same deduced type
7 auto g = []<class T>(T x, T y) {
8 // ...
9 };
10 f(1, 2);
11 g(1, 2);
12 f(1, 2.0);
13 // g(1, 2.0); // ERROR
14 }
■ fact that closure types unnamed causes complications when need arises
to refer to closure type
■ helpful language features: auto, decltype
■ helpful library features: std::function
■ closures can be stored using auto or std::function
■ closures that do not capture can be “stored” by assigning to function
pointer
1 #include <iostream>
2
3 int main()
4 {
5 int count = 5;
6 // Must use mutable in order to be able to
7 // modify count member.
8 auto get_count = [count]() mutable -> int {
9 return count++;
10 };
11
12 int c;
13 while ((c = get_count()) < 10) {
14 std::cout << c << ’\n’;
15 }
16 }
1 #include <iostream>
2 #include <array>
3
4 template <typename T>
5 constexpr auto multiply_by(T i) {
6 return [i](auto j) {return i * j;};
7 // OK: lambda is literal type so members
8 // are automatically constexpr
9 }
10
11 int main() {
12 constexpr auto mult_by_2 = multiply_by(2);
13 std::array<int, mult_by_2(8)> a;
14 std::cout << a.size() << ’\n’;
15 }
1 #include <iostream>
2 #include <vector>
3 #include <functional>
4
5 std::vector<int> vec{2000, 4000, 6000, 8000, 10000};
6 std::function<int(int)> func;
7
8 void do_stuff()
9 {
10 int modulus = 10000;
11 func = [&](int x){return x % modulus;};
12 for (auto x : vec) {
13 std::cout << func(x) << ’\n’;
14 }
15 }
16
17 int main()
18 {
19 do_stuff();
20 for (auto x : vec) {
21 std::cout << func(x) << ’\n’;
22 }
23 }
■ above code has very serious bug; what is it?
References
■ equality relation is rule for set S that specifies for every two elements
a, b ∈ S, if a equals b.
■ for two objects a and b of some type, a and b should be deemed equal if
they represent same logical value
■ consider SimpleString type:
class SimpleString {
bool operator==(const SimpleString& other) const;
// ...
private:
char* start; // start of character buffer
std::size_t length; // length of buffer
};
■ two objects a and b of type SimpleString having same logical value
most likely to be defined as:
2 length members are equal; and
2 buffers pointed to by start members contain identical sequences of
■ in some contexts, can be desirable to treat two objects of some type with
unequal values as equivalent (i.e., as if equal)
■ two values said to be equivalent (in some context) if they are to be treated
as if they were same (even though they might not be equal)
■ equivalence relation is rule for set S that specifies for every two elements
a, b ∈ S, if a equivalent b.
■ consider case-insensitive comparison of two std::string objects
■ in this context, strings with values “hello” and “HELLO” are clearly not
equal but must be treated as if so
■ effectively, equivalence relation partitions set into disjoint subsets where
elements of each subset are equivalent
■ these subsets are known as equivalence classes
■ elements deemed equivalent if and only if belong to same equivalence
class
■ ordering relation is rule for set S that specifies, for every two elements
a, b ∈ S, if a has some particular rank relative to b (e.g., precedes or
follows)
■ for example, each of following built-in operators for type int constitutes
ordering relation for set of all int values:
2 less than (<)
2 greater than (>)
2 less than or equal (<=)
2 greater than or equal (>=)
lesser greater
■ given set {xi } of values of some type T and ordering relations, we can
attempt to place values in order from least to greatest
■ may encounter elements that are incomparable
■ given any two values a and b in set, exactly one of following possibilities
must be true:
1 a precedes b (i.e., a is less than b)
2 a and b are comparable and a does not precede b and b does not precede
a (i.e., a is equivalent to b)
3 b precedes a (i.e., a is greater than b)
2 a and b are comparable and a does not precede b and b does not precede
a (i.e., a is equal to b)
3 b precedes a (i.e., a is greater than b)
■ for example, for set of int values, built-in comparison operators are
consistent with strong ordering
2 a and b are comparable and a does not precede b and b does not precede
a (i.e., a is equivalent to b)
3 b precedes a (i.e., a is greater than b)
2 greater
2 a and b are comparable and a does not precede b and b does not precede
a (i.e., a is equivalent to b)
3 b precedes a (i.e., a is greater than b)
2 greater
2 unordered
2 weak_ordering
3 partial_ordering
■ abbreviated syntax above allows for less verbose code when extracting
two-way comparison result from three-way comparison result
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 542
Comparing Ordering Result and Literal Zero (2)
■ reversing order of operands for three-way comparison operator only has
effect of swapping less and greater:
less if v = greater
{︃
reverse(v) = greater if v = less
v otherwise
■ for each relational operator op (i.e., equality, inequality, less-than,
greater-than, less-than-or-equal, greater-than-or-equal):
0 op v is equivalent to reverse(v) op 0
■ consequently, when literal 0 is first operand, we have:
Expression Equivalent Expression Simplified
0 == v reverse(v) == 0 v == 0
0 != v reverse(v) != 0 v != 0
0 < v reverse(v) < 0 v > 0
0 > v reverse(v) > 0 v < 0
0 <= v reverse(v) <= 0 v >= 0
0 >= v reverse(v) >= 0 v <= 0
0 <=> v reverse(v) <=> 0 reverse(v)
1 #include <cassert>
2 #include <compare>
3 #include <concepts>
4 #include <limits>
5 #include <numbers>
6
7 int main() {
8 constexpr int one = 1;
9 constexpr int two = 2;
10 static_assert(std::numeric_limits<double>::is_iec559);
11 constexpr double nan = std::numeric_limits<double>::quiet_NaN();
12 constexpr double half = 0.5;
13 constexpr double pi = std::numbers::pi_v<double>;
14 static_assert((std::same_as<decltype(one <=> two),
15 std::strong_ordering>));
16 static_assert(one <=> two < 0);
17 static_assert(one <=> one == 0);
18 static_assert(two <=> one > 0);
19 static_assert((std::same_as<decltype(half <=> pi),
20 std::partial_ordering>));
21 static_assert(half <=> pi < 0);
22 static_assert(half <=> half == 0);
23 static_assert(pi <=> half > 0);
24 assert(half <=> nan == std::partial_ordering::unordered);
25 assert(!(half <=> nan == 0) && !(half <=> nan < 0) && !(half <=> nan > 0));
26 }
■ example:
#include <compare>
static_assert(std::is_lt(1 <=> 2));
static_assert(std::is_gt(42.0 <=> 0.0));
1 #include <compare>
2
3 class Point {
4 public:
5 constexpr Point(int x, int y) : x_(x), y_(y) {}
6 constexpr int x() const {return x_;}
7 constexpr int y() const {return y_;}
8 // The following line can be omitted since operator<=> is defaulted:
9 // bool operator==(const Point&) const = default;
10 std::strong_ordering operator<=>(const Point&) const = default;
11 private:
12 int x_;
13 int y_;
14 };
15
16 static_assert(Point(-1, 0) < Point(0, 0));
17 static_assert(Point(0, 1) > Point(0, 0));
18 static_assert(Point(0, -1) < Point(0, 0));
19 static_assert(Point(0, 0) == Point(0, 0));
20 static_assert(Point(1, 0) != Point(0, 0));
21 static_assert(Point(1, 0) > Point(0, 0));
22 static_assert(Point(0, 0) <=> Point(0, 0) == 0);
1 #include <compare>
2 #include <concepts>
3
4 template<class T, class U>
5 constexpr auto synth_three_way(const T& t, const U& u)
6 requires requires {
7 {t < u} -> std::same_as<bool>;
8 {u < t} -> std::same_as<bool>;
9 }
10 {
11 if constexpr(std::three_way_comparable_with<T, U>) {
12 return t <=> u;
13 } else {
14 // assume at least a weak ordering
15 if (t < u) {return std::weak_ordering::less;}
16 if (u < t) {return std::weak_ordering::greater;}
17 return std::weak_ordering::equivalent;
18 }
19 }
1 #include <string>
2
3 class Person {
4 public:
5 Person(const std::string& family_name,
6 const std::string& given_name) :
7 family_name_(family_name), given_name_(given_name) {}
8 std::string family_name() const {return family_name_;}
9 std::string given_name() const {return given_name_;}
10 std::string full_name() const
11 {return family_name_ + ", " + given_name_;}
12 // ...
13 private:
14 std::string family_name_;
15 std::string given_name_;
16 };
1 #include <string>
2
3 class Student {
4 public:
5 Student(const std::string& family_name,
6 const std::string& given_name) :
7 family_name_(family_name), given_name_(given_name) {}
8 // NEW
9 std::string family_name() const {return family_name_;}
10 std::string given_name() const {return given_name_;}
11 std::string full_name() const
12 {return family_name_ + ", " + given_name_;}
13 std::string student_id() {return student_id_;} // NEW
14 private:
15 std::string family_name_;
16 std::string given_name_;
17 std::string student_id_; // NEW
18 };
B C
D E
1 #include <mutex>
2
3 class ThreadSafePolicy {
4 public:
5 void lock() {mutex_.lock();}
6 void unlock() {mutex_.unlock();}
7 private:
8 std::mutex mutex_;
9 };
10
11 class ThreadUnsafePolicy {
12 public:
13 void lock() {} // no-op
14 void unlock() {} // no-op
15 };
16
17 template<class ThreadSafetyPolicy>
18 class Widget : ThreadSafetyPolicy {
19 // ...
20 };
21
22 int main() {
23 Widget<ThreadUnsafePolicy> w;
24 // empty-base optimization (EBO) can be applied
25 // no memory overhead for no-op thread-safety policy
26 }
1 class Base {
2 public:
3 Base() : i_(0.0), j_(0) {}
4 Base(int i) : i_(i), j_(0) {}
5 Base(int i, int j) : i_(i), j_(j) {}
6 // ... (other non-constructor members)
7 private:
8 int i_, j_;
9 };
10
11 class Derived : public Base {
12 public:
13 // inherit non-special constructors from Base
14 // (default constructor not inherited)
15 using Base::Base;
16 // default constructor is implicitly declared and
17 // not inherited
18 };
19
20 int main() {
21 Derived a;
22 // invokes non-inherited Derived::Derived()
23 Derived b(42, 42);
24 // invokes inherited Base::Base(int, int)
25 }
■ order of construction:
1 if most-derived class in hierarchy, initialize all virtual base class objects in
hierarchy in order of depth-first left-to-right traversal of graph of base class
declarations, where left to right refers to order of appearance of base class
names in class definition (virtual base classes to be discussed later)
2 initialize non-virtual (direct) base class objects in order listed in class
definition
3 initialize non-static data members in order of declaration in class definition
4 execute constructor body
■ order of destruction is exact reverse of order of construction, namely:
1 execute destructor body
order
4 if most-derived class in hierarchy, destroy all virtual base class objects in
hierarchy in reverse of construction order
1 #include <iostream>
2
3 template <class T>
4 struct Base {
5 using Real = T;
6 Base(Real x_ = Real()) : x(x_) {}
7 void f() {std::cout << x << "\n";};
8 Real x;
9 };
10
11 template <class T>
12 struct Derived : Base<T> {
13 using Real = typename Base<T>::Real;
14 // OK: Base<T>::Real dependent
15 Derived(Real y_ = Real()) : y(y_) {}
16 void g() {
17 this->x = y; // OK: this->x dependent
18 this->f(); // OK: this->f dependent
19 }
20 Real y;
21 };
22
23 int main() {
24 Derived<double> w(0.0);
25 w.g();
26 }
1 class Worker {
2 public:
3 virtual void prepareEnvelope();
4 // ...
5 };
6
7 class SpecialWorker : public Worker {
8 public:
9 // prevent overriding function responsible for
10 // overall envelope preparation process
11 // but allow functions for individual steps in
12 // process to be overridden
13 void prepareEnvelope() final {
14 stuffEnvelope(); // step 1
15 lickEnvelope(); // step 2
16 sealEnvelope(); // step 3
17 }
18 virtual void stuffEnvelope();
19 virtual void lickEnvelope();
20 virtual void sealEnvelope();
21 // ...
22 };
1 class Base {
2 public:
3 Base() {}
4 ~Base() {} // non-virtual destructor
5 // ...
6 };
7
8 class Derived : public Base {
9 public:
10 Derived() : buffer_(new char[10’000]) {}
11 ~Derived() {delete[] buffer_;}
12 // ...
13 private:
14 char* buffer_;
15 };
16
17 void process(Base* bp) {
18 // ...
19 delete bp; // always invokes only Base::~Base
20 }
21
22 int main() {
23 process(new Base);
24 process(new Derived); // leaks memory
25 }
1 class Base {
2 public:
3 Base() {}
4 virtual ~Base() {} // virtual destructor
5 // ...
6 };
7
8 class Derived : public Base {
9 public:
10 Derived() : buffer_(new char[10’000]) {}
11 ~Derived() {delete[] buffer_;}
12 // ...
13 private:
14 char* buffer_;
15 };
16
17 void process(Base* bp) {
18 // ...
19 delete bp; // invokes destructor polymorphically
20 }
21
22 int main() {
23 process(new Base);
24 process(new Derived);
25 }
■ might want to prevent deriving from class with destructor that is not virtual
■ preventing derivation can sometimes also facilitate better compiler
optimization (e.g., via devirtualization)
■ might want to prevent derivation so that objects can be copied safely
without fear of slicing
1 class Base {
2 public:
3 virtual Base* clone() const {
4 return new Base(*this);
5 }
6 // ...
7 };
8
9 class Derived : public Base {
10 public:
11 // use covariant return type
12 Derived* clone() const override {
13 return new Derived(*this);
14 }
15 // ...
16 };
17
18 int main() {
19 Derived* d = new Derived;
20 Derived* d2 = d->clone();
21 // OK: return type is Derived*
22 // without covariant return type, would need cast:
23 // Derived* d2 = static_cast<Derived*>(d->clone());
24 }
■ pure virtual function can still be defined, although likely only useful in case
of virtual destructor
■ class with one or more pure virtual functions called abstract class
■ cannot directly instantiate objects of abstract class (can only use them as
base class objects)
■ class that derives from abstract class need not override all of its pure
virtual methods
■ class that does not override all pure virtual methods of abstract base class
will also be abstract
■ most commonly, abstract classes have no state (i.e., data members) and
used to provide interfaces, which can be inherited by other classes
■ if class has no pure virtual functions and abstract class is desired, can
make destructor pure virtual (but must provide definition of destructor
since invoked by derived classes)
1 class Abstract {
2 public:
3 virtual ~Abstract() = 0; // pure virtual destructor
4 // ... (no other virtual functions)
5 };
6
7 inline Abstract::~Abstract()
8 { /* possibly empty */ }
■ when derived type known at compile time, may want behavior similar to
virtual functions but without run-time cost (by performing binding at
compile time instead of run time)
■ can be achieved with technique known as curiously-recurring template
pattern (CRTP)
■ class Derived derives from class template instantiation using Derived
itself as template argument
■ example:
template <class Derived>
class Base {
// ...
};
class Derived : public Base<Derived> {
// ...
};
■ language allows derived class to inherit from more than one base class
■ multiple inheritance (MI): deriving from more than one base class
■ although multiple inheritance not best solution for most problems, does
have some compelling use cases
■ one compelling use case is for inheriting interfaces by deriving from
abstract base classes with no data members
■ when misused, multiple inheritance can lead to very convoluted code
■ in multiple inheritance contexts, ambiguities in naming can arise
■ for example, if class Derived inherits from classes Base1 and Base2,
each of which have member called x, name x can be ambiguous in some
contexts
■ scope resolution operator can be used to resolve ambiguous names
1 class Base1 {
2 public:
3 void func();
4 // ...
5 };
6
7 class Base2 {
8 void func();
9 // ...
10 };
11
12 class Derived : public Base1, public Base2 {
13 public:
14 // ...
15 };
16
17 int main() {
18 Derived d;
19 d.func(); // ERROR: ambiguous function call
20 d.Base1::func(); // OK: invokes Base1::func
21 d.Base2::func(); // OK: invokes Base2::func
22 }
1 class Input_stream {
2 public:
3 virtual ~Input_stream() {}
4 virtual int read_char() = 0;
5 virtual int read(char* buffer, int size) = 0;
6 virtual bool is_input_ready() const = 0;
7 // ...(all pure virtual, no data)
8 };
9
10 class Output_stream {
11 public:
12 virtual ~Output_stream() {}
13 virtual int write_char(char c) = 0;
14 virtual int write(char* buffer, int size) = 0;
15 virtual int flush_output() = 0;
16 // ... (all pure virtual, no data)
17 };
18
19 class Input_output_stream : public Input_stream,
20 public Output_stream {
21 // ...
22 };
B C
1 class Base {
2 public:
3 // ...
4 protected:
5 int data_;
6 };
7
8 class D1 : public Base { /* ... */ };
9
10 class D2 : public Base { /* ... */ };
11
12 class Join : public D1, public D2 {
13 public:
14 void method() {
15 data_ = 1; // ERROR: ambiguous
16 D1::data_ = 1; // OK: unambiguous
17 }
18 };
19
20 int main() {
21 Join* j = new Join();
22 Base* b;
23 b = j; // ERROR: ambiguous
24 b = static_cast<D1*>(j); // OK: unambiguous
25 }
■ when using multiple inheritance, may want to ensure that only one
instance of base-class object can appear in derived-class object
■ virtual base class: base class that is only ever included once in derived
class, even if derived from multiple times
■ virtual inheritance: when derived class inherits from base class that is
virtual
■ virtual inheritance can be used to avoid situations like dreaded diamond
pattern
■ order of construction: virtual base classes constructed first in depth-first
left-to-right traversal of graph of base classes, where left-to-right refers to
order of appearance of base class names in class definition
1 class Base {
2 public:
3 // ...
4 protected:
5 int data_;
6 };
7
8 class D1 : public virtual Base { /* ... */ };
9
10 class D2 : public virtual Base { /* ... */ };
11
12 class Join : public D1, public D2 {
13 public:
14 void method() {
15 data_ = 1; // OK: unambiguous
16 }
17 };
18
19 int main() {
20 Join* j = new Join();
21 Base* b = j; // OK: unambiguous
22 }
References
1 N. Meyers. The empty base C++ optimization. Dr. Dobb’s Journal, Aug.
1997. Available online at http://www.cantrip.org/emptyopt.html.
2 J. O. Coplien. Curiously recurring template patterns. C++ Report, pages
24–27, Feb. 1995.
3 S. Meyers. Counting objects in C++. C++ User’s Journal, Apr. 1998.
Available online at http:
//www.drdobbs.com/cpp/counting-objects-in-c/184403484.
4 A. Nasonov. Better encapsulation for the curiously recurring template
pattern. Overload, 70:11–13, Dec. 2005.
Modules
Compilation Model
Source File
Headers Included
Directly or Indirectly Compile Object File
by Source File
Programmer’s View
a.hpp
// A-1
#include "b.hpp" Compiler’s View
// A-2
Translation Unit To Be Compiled
b.hpp // A-1
// B
// B // A-2
int main() {
// ...
main.hpp }
#include "a.hpp"
int main() {
// ...
}
util_2.cpp
#include <vector>
// ... (code that uses std::vector)
main.cpp
#include <vector>
int main() {
// ... code that uses std::vector
}
■ when multiple source files include same header, this leads to repeated compilation
of same code
■ building program comprised of above three source files requires compiling code
included from vector header three times
■ furthermore, amount of code added by inclusion of vector header not small (e.g.,
on order of tens of thousands of lines of code)
hg2g.hpp
1 #ifndef HG2G_HPP app.cpp
2 #define HG2G_HPP
3 #ifdef assert 1 #include <cassert>
4 #undef assert 2 #include "hg2g.hpp"
5 #endif 3
6 #define assert(x) /* Don’t panic! */ 4 int main() {
7 namespace hg2g { 5 auto i = hg2g::get_answer(); // OK
8 namespace detail { 6 auto j = hg2g::detail::helper();
9 using c42_t = char[42]; 7 // BAD: access to implementation
10 int helper() 8 // detail is possible
11 {return sizeof(c42_t);}; 9 assert(i == 42);
12 } 10 // BAD: assert is noop
13 int get_answer() 11 return i == 42 ? 0 : 1;
14 {return detail::helper();} 12 }
15 }
16 #endif
■ all declarations in header visible to includer, which can often expose many
implementation details
■ header can define, undefine, or redefine macros in manner that includer
does not expect (and vice versa), resulting in incorrect code behavior
app.cpp
1 #include "util.hpp"
2 #include <cassert>
3
4 int main() {
5 assert(false);
6 // BAD: behavior depends on order of include directives above
7 }
Source File
Headers Included
Compile Object File
By Source File
Source File
Object File
Headers Included
Compile (If Needed in Addition
By Source File
to Output CMI)
Without Modules
1 # 1. compile all source files in any order
2 g++ -std=c++20 -c greetings.cpp
3 g++ -std=c++20 -c greet.cpp
4 # 2. link
5 g++ -std=c++20 -o greet greet.o greetings.o
With Modules
1 # 0. set base compiler flags
2 cxxflags="-std=c++20 -fmodules-ts"
3 # 1. generate system header units
4 g++ $cxxflags -x c++-system-header -c iostream
5 g++ $cxxflags -x c++-system-header -c string
6 # 2. generate CMI and object files for module interface units
7 # in dependency order
8 g++ $cxxflags -c greetings-m.cpp
9 # 3. compile remaining source files in any order
10 g++ $cxxflags -c greetings.cpp
11 g++ $cxxflags -c greet.cpp
12 # 4. link
13 g++ $cxxflags -o greet greet.o greetings-m.o greetings.o
Without Modules
1 # 1. compile all source files in any order
2 clang++ -std=c++20 -c greetings.cpp
3 clang++ -std=c++20 -c greet.cpp
4 # 2. link
5 clang++ -std=c++20 -o greet greet.o greetings.o
With Modules
1 # 0. set base compiler flags
2 cxxflags="-std=c++20 -fmodules -stdlib=libc++ -fprebuilt-module-path=."
3 # 1. generate CMIs for module interface units in dependency order
4 clang++ $cxxflags --precompile -x c++-module -o greetings.pcm \
5 -c greetings-m.cpp
6 # 2. compile remaining source files in any order
7 clang++ $cxxflags -c greetings-m.cpp
8 clang++ $cxxflags -c -fmodule-file=greetings.pcm greetings.cpp
9 clang++ $cxxflags -c greet.cpp
10 # 3. link
11 clang++ $cxxflags -o greet greet.o greetings-m.o greetings.o
Without Modules
Makefile
1 # This makefile should work with either GCC or Clang.
2 CXXFLAGS = -std=c++20
3 PROGRAMS = greet
4
5 .PHONY: all
6 all: $(PROGRAMS)
7
8 .PHONY: clean
9 clean:
10 rm -f *.o $(PROGRAMS)
11
12 greet: greet.o greetings.o
13 $(CXX) $(CXXFLAGS) -o $@ $^
With Modules
Makefile.gcc
1 # This makefile is known to work with a development version of GCC 11.0.0.
2 CXX = g++
3 CXXFLAGS = -std=c++20 -fmodules-ts
4 PROGRAM = greet
5 HEADER_UNITS = iostream string
6
7 .PHONY: all
8 all: $(PROGRAM)
9
10 .PHONY: clean
11 clean:
12 rm -f $(PROGRAM) *.o *.cmi header_units
13 rm -rf gcm.cache
14
15 header_units:
16 for i in $(HEADER_UNITS); do \
17 $(CXX) $(CXXFLAGS) -x c++-system-header -c $$i || exit 1; \
18 done
19 touch header_units
20 greet.cmi: header_units
21 $(CXX) $(CXXFLAGS) -c greetings-m.cpp
22 touch greet.cmi
23 greetings.o: header_units greet.cmi
24 $(CXX) $(CXXFLAGS) -c greetings.cpp
25 greet.o: header_units greet.cmi
26 $(CXX) $(CXXFLAGS) -c greet.cpp
27 greet: greet.o greetings-m.o greetings.o
28 $(CXX) $(CXXFLAGS) -o greet $^
2 import header_name;
greetings-m.cpp (Module)
1 export module greetings;
2
3 import <string>; // import header
4
5 export namespace greetings {
6 std::string get_greeting() {return "Hello, World!";}
7 }
module
2 implementation unit, which provides implementation of module’s interface
■ interface (and corresponding implementation) for module can be split
across multiple translation units by using what are called partition units
■ every module unit can also be classified as either partition unit or
non-partition unit
■ module interface unit that is not partition unit called primary module
interface unit
■ only identifiers that module exports are visible to importer of module
■ any code that wants access to exported identifiers of module must first
import that module
■ cannot export entities with internal linkage (e.g., static variables and
functions, and functions/variables/classes defined in anonymous
namespaces)
■ first declaration of exported entity must be exported declaration, while
subsequent declarations may omit export keyword
■ exporting imported module exports all identifiers exported in imported
module
■ exporting namespace block exports all identifiers declared in namespace
block and requires that all identifiers in block are exportable
■ exporting entity in namespace implicitly exports name of containing
namespace
■ using declaration can be exported unless referred to entity has internal or
module linkage
■ module partitions provide means to split large interface into smaller pieces
■ as mentioned earlier, module declaration declares module partition if
partition name specified for module
■ for example: export module foo:bar;
■ any number of module partition interface units allowed
■ module interface consists of everything specified in primary interface and
all partition interfaces
■ primary module interface unit must export all module partition interface
units; otherwise program is ill formed with no diagnostic required
■ module partitions can be imported only by other module units in same
module (i.e, partitions invisible outside of their module)
greet.cpp
1 import <iostream>;
2 import greetings;
3
4 int main() {
5 std::cout << greetings::get_greeting() << std::endl;
6 return !std::cout;
7 }
1 #include <cassert>
2
3 auto func() {
4 struct widget {
5 int value;
6 };
7 return widget{42};
8 }
9
10 int main() {
11 // widget is not visible
12 // widget is reachable via return type of func
13 auto x = func();
14 // declare name for reachable type
15 using thing = decltype(x);
16 thing w{0};
17 assert(w.value == 0);
18 return w.value;
19 }
zeus-m.cpp
1 export module zeus;
2 namespace zeus {
3 struct int_point {int x; int y;};
4 export int_point origin() {return {0, 0};}
5 }
main.cpp
1 #include <cassert>
2 import zeus;
3
4 int main() {
5 // zeus::int_point q; // ERROR: int_point not visible
6 auto p = zeus::origin();
7 assert(p.x == 0 && p.y == 0);
8 using point = decltype(p);
9 point q;
10 }
■ name declared in different scopes or in same scope more than once can
be made to refer to same entity through notion of linkage
■ in order words, linkage determines whether two entities declared with
same name are same entity or two distinct entities (where declarations in
question may be in same or different translation units)
■ notion of linkage applies to names of variables, functions, namespaces,
and so on
■ language has four types of linkage:
1 none
2 internal
3 module
4 external
sol-m.cpp
1 export module sol;
2 namespace {
3 int x = 42; // x has internal linkage
4 bool yes() {return true;} // yes has internal linkage
5 }
6 namespace sol {
7 static int y = 0; // y has internal linkage
8 int z = -1; // z has module linkage
9 export inline constexpr int forty_two() {return 42;}
10 // forty_two has external linkage
11 int nil() {return 0;} // nil has module linkage
12 export int value() {return forty_two() + nil();}
13 // value has external linkage
14 int foo() // foo has module linkage
15 {
16 struct S {}; // S has no linkage
17 int i = 0; // i has no linkage
18 return yes() + i + sizeof(S);
19 }
20 }
■ can place source for module in single file or split across multiple files
■ advantages of using single file:
2 can avoid repetition
2 facilitates module interface only library
■ disadvantages of using single file:
2 unnecessary recompilation (when implementation changed but not
interface)
2 file containing interface specification also cluttered with implementation
details
2 may introduce additional dependencies due to implementation having
dependencies that are not required by interface
2 reduced compilation speed for interface unit
1 Bryce Adelstein Lelbach. Modules are Coming. Core C++, Tel-Aviv, Israel,
May 16, 2019. Available online at https://youtu.be/bDTm6y6fNSU.
This talk offers a very good introduction to C++20 modules.
2 Gabriel Dos Reis. C++ Modules: What You Should Know. CPPP, Paris,
France, June 15, 2019. Available online at
https://youtu.be/MP6SJEBt6Ss. [This talk provides a high-level
overview of modules (e.g., motivations and constraints), but does not
provide many detailed examples of usage.]
3 Boris Kolpackov. Practical C++ Modules. CppCon, Aurora, CO, USA,
Sept. 17, 2019. Available online at https://youtu.be/szHV6RdQdg8.
[This talk more of an emphasis on how to use modules in practice, rather
than the language aspects of modules.]
4 Michael Spencer. Building Modules. CppCon, Aurora, CO, USA, Sept. 20,
2019. Available online at https://youtu.be/L0SHHkBenss.
Coroutines
call A resume A
return suspend
resume A
return
1 #include <iostream>
2 #include "simple_task.hpp"
3
4 simple_task<int> greet() {
5 std::cout << "hello\n";
6 co_await std::suspend_always();
7 std::cout << "bonjour\n";
8 co_await std::suspend_always();
9 std::cout << "hola\n";
10 co_return !std::cout.flush();
11 }
12
13 int main() {
14 auto g = greet();
15 while (g.resume()) {}
16 return g.value();
17 }
1 #include <iostream>
2 #include <string>
3 #include "generator.hpp"
4
5 using namespace std::literals;
6
7 generator<char> greet() {
8 for (auto&& c : "Hello, World!\n"s) {
9 co_yield c;
10 }
11 }
12
13 int main() {
14 for (auto&& c : greet()) {
15 std::cout << c;
16 }
17 return !std::cout.flush();
18 }
1 #include <iostream>
2 #include "generator.hpp"
3
4 generator<unsigned long long> fibonacci() {
5 unsigned long long f[]{0, 1};
6 for (;;) {
7 co_yield f[0];
8 auto tmp = f[0] + f[1];
9 f[0] = f[1];
10 f[1] = tmp;
11 }
12 }
13
14 int main() {
15 for (auto&& x : fibonacci()) {
16 if (x >= 1’000) {break;}
17 std::cout << x << ’\n’;
18 }
19 }
■ when function called, compiler must construct stack frame that contains:
2 arguments
2 local variables
2 return value
2 temporary storage for registers
■ when coroutine invoked, compiler must construct coroutine frame that
contains:
2 formal parameters
2 local variables
2 selected temporaries
2 execution state for coroutine needed to resume (e.g., registers and IP)
2 promise object used to return value or values to caller
■ generally, coroutine frame dynamically allocated
■ coroutine frame created before coroutine starts running
■ compiler provides handle to coroutine frame to caller of coroutine
■ promise type is type of promise object, which is used to pass value from
coroutine back to caller
■ compiler automatically generates local variable of coroutine with promise
type
■ promise type only needed if coroutine passes values back to caller
■ promise type is class type that must provide certain members
1 {
2 promise-type promise promise-constructor-arguments;
3 try {
4 initial-suspend:
5 co_await promise.initial_suspend();
6 function-body
7 } catch (...) {
8 if (!initial-await-resume-called)
9 throw;
10 promise.unhandled_exception();
11 }
12 final-suspend:
13 co_await promise.final_suspend();
14 }
■ coroutine has general structure shown above [C++20 §9.5.4/5]
⁓⁓⁓⁓⁓⁓⁓⁓
■ initial-await-resume-called initially false and set to true immediately
before evaluation of await_resume expression of initial suspend point
1 #include <iostream>
2 #include "simple_task.hpp"
3
4 simple_task<int> greet() {
5 std::cout << "hello\n";
6 co_await std::suspend_always();
7 std::cout << "bonjour\n";
8 co_await std::suspend_always();
9 std::cout << "hola\n";
10 co_return !std::cout.flush();
11 }
12
13 int main() {
14 auto g = greet();
15 while (g.resume()) {}
16 return g.value();
17 }
1 #include <coroutine>
2 #include <exception>
3 #include <utility>
4
5 template <class T> struct simple_task_promise {
6 public:
7 auto get_return_object()
8 {return std::coroutine_handle<simple_task_promise>::from_promise(*this);}
9 std::suspend_always initial_suspend() {return {};}
10 std::suspend_always final_suspend() noexcept {return {};}
11 void return_value(T value) {value_ = std::move(value);}
12 void unhandled_exception() {std::terminate();}
13 const T& value() const {return value_;}
14 private:
15 T value_;
16 };
17
18 template<> struct simple_task_promise<void> {
19 public:
20 auto get_return_object()
21 {return std::coroutine_handle<simple_task_promise>::from_promise(*this);}
22 std::suspend_always initial_suspend() {return {};}
23 std::suspend_always final_suspend() {return {};}
24 void return_void() {}
25 void unhandled_exception() {std::terminate();}
26 };
■ bool await_ready();
2 tests if awaiter object is ready
■ Suspend await_suspend(coroutine_handle<promise_type> h);
2 specifies action to be taken when coroutine suspended (which may involve
resuming current or other coroutine)
2 Suspend can be std::coroutine_handle<T>, bool, or void
■ Value await_resume();
2 called when coroutine resumed to generate result from co_await
struct suspend_always {
constexpr bool await_ready() const noexcept
{return false;}
constexpr void await_suspend(coroutine_handle<>)
const noexcept {}
constexpr void await_resume() const noexcept {}
};
■ useful in situations where coroutine should always be suspended but
should not yield value when resumed
struct suspend_never {
constexpr bool await_ready() const noexcept
{return true;}
constexpr void await_suspend(coroutine_handle<>)
const noexcept {}
constexpr void await_resume() const noexcept {}
};
■ useful in contexts where coroutine should not be suspended
More Examples
■ fsm class provides algorithmic framework for finite state machine (FSM)
■ coroutine implements rules for state transitions in FSM
■ caller passes next event into coroutine via resume function member of
resumable type
■ caller invokes resume member function in loop, passing next event for
FSM to coroutine via this function
■ coroutine passes next state back to caller via yield/await expression
■ does not suspend at initial suspend point; only suspends at final suspect
point
1 #include <coroutine>
2 #include <exception>
3 #include <iterator>
4 #include <utility>
5
6 template <class, class> class fsm;
7
8 template <class Promise>
9 class fsm_awaiter {
10 public:
11 bool await_ready() {return false;}
12 void await_suspend(std::coroutine_handle<Promise> handle) {
13 handle_ = handle;
14 }
15 typename Promise::event_type await_resume() {
16 return handle_.promise().get_event();
17 }
18 private:
19 std::coroutine_handle<Promise> handle_ = nullptr;
20 };
1 #include <coroutine>
2 #include <exception>
3 #include <iterator>
4 #include <utility>
5
6 template <class> class generator;
7
8 template <class T>
9 class generator_promise {
10 public:
11 using value_type = std::remove_reference_t<T>;
12 using coroutine_handle = typename
13 std::coroutine_handle<generator_promise<T>>;
14 generator<T> get_return_object()
15 {return coroutine_handle::from_promise(*this);}
16 std::suspend_always initial_suspend() {return {};}
17 std::suspend_always final_suspend() noexcept {return {};}
18 std::suspend_always yield_value(value_type result) {
19 result_ = std::move(result);
20 return {};
21 }
22 void unhandled_exception() {std::terminate();}
23 value_type& value() noexcept {return result_;}
24 private:
25 value_type result_;
26 };
■ task is class template that provides means to wait for some computation
to complete, possibly returning result of computation (if any) having some
specific type
■ task is awaiter type (and is therefore awaitable type)
■ can co_await on task object to wait for task to complete and obtain
return value (if any)
■ at initial suspension point, always suspends
■ at final suspension point, always suspends and resumes coroutine
coawaiting result of task
1 #include <coroutine>
2 #include <utility>
3 #include <variant>
4
5 template<class Promise_type> struct task_final_awaiter {
6 bool await_ready() noexcept {return false;}
7 void await_resume() noexcept {}
8 auto await_suspend(std::coroutine_handle<Promise_type> me) noexcept
9 {return me.promise().waiter;}
10 };
11
12 template<class T> struct task_promise_type {
13 auto get_return_object()
14 {return std::coroutine_handle<task_promise_type>::from_promise(*this);}
15 void return_value(T value)
16 {result.template emplace<2>(std::move(value));}
17 void unhandled_exception()
18 {result.template emplace<1>(std::current_exception());}
19 std::suspend_always initial_suspend() {return {};}
20 task_final_awaiter<task_promise_type<T>> final_suspend() noexcept
21 {return {};}
22 std::variant<std::monostate, std::exception_ptr, T> result;
23 std::coroutine_handle<> waiter;
24 };
1 #include <iostream>
2 #include "sync_wait.hpp"
3
4 task<int> f() {
5 co_return 42;
6 }
7
8 task<int> g() {
9 co_return co_await f() + co_await f();
10 }
11
12 task<void> h() {
13 std::cout << "hello world\n";
14 co_return;
15 }
16
17 int main()
18 {
19 std::cout << sync_wait(f()) << ’\n’;
20 std::cout << sync_wait(g()) << ’\n’;
21 sync_wait(h());
22 }
■ may want to invoke task from non-coroutine and then wait for task to
complete
■ cannot use co_await to wait for task to complete, since co_await
can only be used in coroutine
■ create dummy task (which suspends at initial suspension point)
■ dummy task set to be resumed when real task reaches final suspension
point
■ thus, resumption of dummy task indicates real task complete
■ use latch as means for sync_wait to wait for dummy task to resume
■ use await_resume for real task to obtain value returned from real task
1 #include <coroutine>
2 #include <latch>
3 #include "task.hpp"
4
5 template <class Awaiter> auto sync_wait(Awaiter x) {
6 if (!x.await_ready()) {
7 std::latch waiters(1);
8 auto helper = [&]()->task<void> {
9 waiters.count_down();
10 co_return;
11 };
12 auto helper_task = helper();
13 auto helper_handle = helper_task.handle();
14 helper_handle.promise().waiter = std::noop_coroutine();
15 x.await_suspend(helper_handle);
16 waiters.wait();
17 }
18 return x.await_resume();
19 }
Additional Examples
References
Sequence Containers
Name Description
array fixed-size array
vector dynamic-size array
deque double-ended queue
forward_list singly-linked list
list doubly-linked list
Container Adapters
Name Description
stack stack
queue FIFO queue
priority_queue priority queue
1 #include <iostream>
2 #include <vector>
3
4 int main() {
5 std::vector<int> values;
6
7 // append elements with values 0 to 9
8 for (int i = 0; i < 10; ++i) {
9 values.push_back(i);
10 }
11
12 // print each element followed by space
13 for (int i = 0; i < values.size(); ++i) {
14 std::cout << values[i] << ’ ’;
15 }
16 std::cout << ’\n’;
17 }
18
19 /* This program produces the following output:
20 0 1 2 3 4 5 6 7 8 9
21 */
begin end
■ organization of elements in doubly-linked list container:
begin end
begin end
■ suppose we want to set all elements in container to zero
■ we could use code like:
// int* begin; int* end;
for (int* iter = begin; iter != end; ++iter)
*iter = 0;
Expression Effect
T(a) copies iterator (copy constructor)
*a dereference as rvalue (i.e., read only); cannot
a->m dereference at old position
++a steps forward (returns new position)
a++ steps forward
a == b test for equality
a != b test for inequality
■ not assignable (i.e., no assignment operator)
Expression Effect
T(a) copies iterator (copy constructor)
*a dereference as lvalue (i.e., write only); can only
a->m be dereferenced once; cannot dereference at old
position
++a steps forward (returns new position)
a++ steps forward (returns old position)
■ not assignable (i.e., no assignment operator)
■ no comparison operators (i.e., operator==, operator!=)
Expression Effect
T() default constructor
T(a) copy constructor
a = b assignment
*a dereference
a->m
++a steps forward (returns new position)
a++ steps forward (returns old position)
a == b test for equality
a != b test for inequality
■ must ensure that valid to dereference iterator before doing so
Forward Iterator
Bidirectional Iterator
Random-Access Iterator
Contiguous Iterator
1 #include <iostream>
2 #include <vector>
3
4 int main() {
5 std::vector<int> values(10);
6
7 std::cout << "number of elements: " <<
8 (values.end() - values.begin()) << ’\n’;
9
10 // initialize elements of vector to 0, 1, 2, ...
11 for (std::vector<int>::iterator i = values.begin();
12 i != values.end(); ++i) {
13 *i = i - values.begin();
14 }
15
16 // print elements of vector
17 for (std::vector<int>::const_iterator i =
18 values.cbegin(); i != values.cend(); ++i) {
19 std::cout << ’ ’ << *i;
20 }
21 std::cout << ’\n’;
22 }
Partition Operations
Name Description
is_partitioned test if range is partitioned by predicate
partition partition range in two
partition_copy copies range partition in two
stable_partition partition range in two (stable ordering)
partition_point get partition point
Sorting
Name Description
is_sorted test if range is sorted
is_sorted_until find first unsorted element in range
sort sort elements in range
stable_sort sort elements in range, preserving order of equiv-
alents
partial_sort partially sort elements in range
partial_sort_copy copy and partially sort range
nth_element sort element in range
Heap Operations
Name Description
is_heap test if range is heap
is_heap_until first first element not in heap order
push_heap push element into heap range
pop_heap pop element from heap range
make_heap make heap from range
sort_heap sort elements of heap
Minimum/Maximum
Name Description
min get minimum of given values
max get maximum of given values
minmax get minimum and maximum of given values
min_element get smallest element in range
max_element get largest element in range
minmax_element get smallest and largest elements in range
clamp clamp value between pair of boundary values
lexicographic_compare lexicographic less-than comparison
is_permutation test if range permutation of another
next_permutation transform range to next permutation
prev_permutation transform range to previous permutation
Numeric Operations
Name Description
iota fill range with successive values
accumulate accumulate values in range
adjacent_difference compute adjacent difference of range
inner_product compute inner product of range
partial_sum compute partial sums of range
reduce similar to accumulate except out of order
exclusive_scan similar to partial_sum, excludes ith input ele-
ment from ith sum
inclusive_scan similar to partial_sum, includes ith input ele-
ment in ith sum
transform_reduce applies functor, then reduces out of order
transform_exclusive_scan applies functor then, calculates exclusive scan
transform_inclusive_scan applies functor, then calculates inclusive scan
.Functions
. . . . . . . . . . . .for
. . . .Uninitialized
. . . . . . . . . . . . .. .Storage
........
1 #include <iostream>
2 #include <vector>
3 #include <algorithm>
4 #include <random>
5
6 int main() {
7 std::vector<int> values;
8
9 int x;
10 while (std::cin >> x) {values.push_back(x);}
11
12 std::cout << "zero count: " << std::count(
13 values.begin(), values.end(), 0) << ’\n’;
14
15 std::default_random_engine engine;
16 std::shuffle(values.begin(), values.end(), engine);
17 std::cout << "random order:";
18 for (auto i : values) {std::cout << ’ ’ << i;}
19 std::cout << ’\n’;
20
21 std::sort(values.begin(), values.end());
22 std::cout << "sorted order:";
23 for (auto i : values) {std::cout << ’ ’ << i;}
24 std::cout << ’\n’;
25 }
1 #include <iostream>
2 #include <vector>
3 #include <algorithm>
4
5 struct MultiplyBy { // Functor class
6 MultiplyBy(double factor) : factor_(factor) {}
7 double operator()(double x) const
8 {return factor_ * x;}
9 private:
10 double factor_; // multiplicative factor
11 };
12
13 int main() {
14 MultiplyBy mb(2.0);
15 std::vector v{1.0, 2.0, 3.0};
16 // v contains 1 2 3
17 std::transform(v.begin(), v.end(), v.begin(), mb);
18 // v contains 2 4 6
19 for (auto i : v) {std::cout << i << ’\n’;}
20 }
Iterators
Member Name Description
begin return iterator to beginning
end return iterator to end
cbegin return const iterator to beginning
cend return const iterator to end
rbegin return reverse iterator to beginning
rend return reverse iterator to end
crbegin return const reverse iterator to beginning
crend return const reverse iterator to end
Element Access
Member Name Description
operator[] access element (no bounds checking)
at access element (with bounds checking)
front access first element
back access last element
data return pointer to start of element data
Modifiers
Member Name Description
fill fill container with specified value
swap swap contents of two arrays
1 #include <array>
2 #include <iostream>
3 #include <algorithm>
4 #include <experimental/iterator>
5
6 int main() {
7 std::array<int, 3> a1{3, 1, 2};
8 std::array<int, 3> a2;
9 a2.fill(42);
10 for (auto i : a2) {
11 std::cout << i << ’\n’;
12 }
13 a2 = a1;
14 std::sort(a1.begin(), a1.end());
15 std::copy(a1.begin(), a1.end(),
16 std::experimental::make_ostream_joiner(std::cout, ", "));
17 std::cout << ’\n’;
18 for(auto i = a2.begin(); i != a2.end(); ++i) {
19 std::cout << *i;
20 if (i != a2.end() - 1) {std::cout << ", ";}
21 }
22 std::cout << ’\n’;
23 }
1 #include <array>
2 #include <iostream>
3 #include <algorithm>
4
5 int main() {
6 // Fixed-size array with 4 elements.
7 std::array<int, 4> a{2, 4, 3, 1};
8
9 // Print elements of array.
10 for (auto i = a.cbegin(); i != a.cend(); ++i) {
11 std::cout << ’ ’ << *i;
12 }
13 std::cout << ’\n’;
14
15 // Sort elements of array.
16 std::sort(a.begin(), a.end());
17
18 // Print elements of array.
19 for (auto i = a.cbegin(); i != a.cend(); ++i) {
20 std::cout << ’ ’ << *i;
21 }
22 std::cout << ’\n’;
23 }
Iterators
Member Name Description
begin return iterator to beginning
end return iterator to end
cbegin return const iterator to beginning
cend return const iterator to end
rbegin return reverse iterator to beginning
rend return reverse iterator to end
crbegin return const reverse iterator to beginning
crend return const reverse iterator to end
Element Access
Member Name Description
operator[] access element (no bounds checking)
at access element (with bounds checking)
front access first element
back access last element
data return pointer to start of element data
Modifiers
Member Name Description
clear clear content
assign assign vector content
insert insert elements
emplace insert element, constructing in place
push_back add element at end
emplace_back insert element at end, constructing in place
erase erase elements
pop_back delete last element
resize change size
swap swap content of two vectors
Allocator
Member Name Description
get_allocator get allocator used by vector
a b c
start i
■ push_back(d) invoked
■ new larger array is allocated (say, twice size of original); elements in old array
moved/copied to new array; then new element added
? ? ? a b c d unused unused
i start
■ elements in old array destroyed and memory for old array deallocated; iterator i
is now invalid:
a b c d unused unused
i start
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 823
vector Example: Constructors
1 std::vector<double> v0;
2 // empty vector
3
4 std::vector<double> v1(10);
5 // vector with 10 elements, each initialized to 0.0
6 // (effectively via value initialization)
7
8 std::vector<double> v2(10, 5.0);
9 // vector with 10 elements, each initialized to 5.0
10
11 std::vector<int> v3{1, 2, 3};
12 // vector with 3 elements: 1, 2, 3
13 // std::initializer_list (note brace brackets)
1 #include <iostream>
2 #include <vector>
3
4 int main() {
5 std::vector<std::vector<int>> v{{1, 2, 3}, {4, 5, 6}};
6 v.emplace_back(10, 0);
7 // The above use of emplace_back is more efficient than:
8 // v.push_back(std::vector<int>(10, 0));
9 for (const auto& i : v) {
10 for (const auto& j : i) {
11 std::cout << ’ ’ << j;
12 }
13 std::cout << ’\n’;
14 }
15 }
■ program output:
1 2 3
4 5 6
0 0 0 0 0 0 0 0 0 0
Iterators
Member Name Description
begin return iterator to beginning
end return iterator to end
cbegin return const iterator to beginning
cend return const iterator to end
rbegin return reverse iterator to reverse beginning
rend return reverse iterator to reverse end
crbegin return const reverse iterator to reverse beginning
crend return const reverse iterator to reverse end
Element Access
Member Name Description
operator[] access character in string (no bounds checking)
at access character in string (with bounds checking)
front access first character in string
back access last character in string
Operations
Member Name Description
clear clear string
assign assign content to string
insert insert into string
push_back append character to string
operator+= append to string
append append to string
erase erase characters from string
pop_back delete last character from string
replace replace part of string
resize resize string
swap swap contents with another string
Search
Member Name Description
find find first occurrence of content in string
rfind find last occurrence of content in string
find_first_of find first occurrence of characters in string
find_first_not_of find first absence of characters in string
find_last_of find last occurrence of characters in string
find_last_not_of find last absence of characters in string
Allocator
Member Name Description
get_allocator get allocator
Numeric Conversions
Name Description
stoi convert string to int
stol convert string to long
stoll convert string to long long
stoul convert string to unsigned long
stoull convert string to unsigned long long
stof convert string to float
stod convert string to double
stold convert string to long double
to_string convert integral or floating-point value to string
to_wstring convert integral or floating-point value to wstring
1 #include <iostream>
2 #include <string>
3
4 int main() {
5 std::string s;
6 if (!(std::cin >> s)) {
7 s.clear();
8 }
9 std::cout << "string: " << s << ’\n’;
10 std::cout << "length: " << s.size() << ’\n’;
11 std::string b;
12 for (auto i = s.crbegin(); i != s.crend(); ++i) {
13 b.push_back(*i);
14 }
15 std::cout << "backwards: " << b << ’\n’;
16
17 std::string msg = "Hello";
18 msg += ", World!"; // append ", World!"
19 std::cout << msg << ’\n’;
20
21 const char* cstr = s.c_str();
22 std::cout << "C-style string: " << cstr << ’\n’;
23 }
1 #include <iostream>
2 #include <string>
3
4 int main() {
5 double x = 42.24;
6 // Convert double to string.
7 std::string s = std::to_string(x);
8 std::cout << s << ’\n’;
9
10 s = "3.14";
11 // Convert string to double.
12 x = std::stod(s);
13 std::cout << x << ’\n’;
14
15 }
1 #include <tuple>
2 #include <cassert>
3
4 int main() {
5 std::pair p(true, 42);
6 assert(p.first && p.second == 42);
7 assert(p.first == std::get<0>(p) &&
8 p.second == std::get<1>(p));
9 std::pair q(true, 42);
10 assert(p == q);
11 p = {false, 0};
12 assert(p != q);
13 p.swap(q);
14 auto [b, i] = p;
15 assert(b == true && i == 42);
16 assert(std::get<bool>(p) && std::get<0>(p));
17 assert(std::get<int>(p) == 42 &&
18 std::get<1>(p) == 42);
19 }
1 #include <tuple>
2 #include <cassert>
3
4 int main() {
5 std::tuple t(true, 42, ’Z’);
6 auto u = std::tuple(true, 42, ’Z’);
7 assert(t == u);
8 assert(std::get<bool>(t) && std::get<0>(t));
9 assert(std::get<char>(t) == ’Z’ && std::get<2>(t) == ’Z’);
10 std::get<0>(t) = false;
11 assert(t != u);
12 std::tuple v(false, 0, ’0’);
13 u = std::tuple(true, 1, ’1’);
14 v.swap(u);
15 assert(std::get<0>(v));
16 }
■ simple container that manages optional value (i.e., value that may or may
not be present)
■ declaration:
template <class T> class optional;
■ T is type of optional value
■ T cannot be reference type
■ at any given point in time, object either contains value or does not
■ object can be given value by initialization or assignment
■ common use case is return value of function that can fail
■ std::bad_optional_access exception indicates checked access to
optional object that does not contain value
■ optional value is required to be stored directly in optional object itself
[C++17 §23.6.3/1]
⁓⁓⁓⁓⁓⁓⁓⁓⁓
Observers
Name Description
operator-> accesses contained value
operator* accesses contained value
operator bool tests if object contains value
has_value tests if object contains value
value returns contained value
value_or returns contained value if available and spec-
ified default value otherwise
Modifiers
Name Description
swap exchange contents
reset clear any contained value
emplace constructs contained value in place
1 #include <optional>
2 #include <string>
3 #include <fstream>
4 #include <iostream>
5
6 std::optional<std::string> read_file(const char* file_name) {
7 std::ifstream in(file_name);
8 std::optional<std::string> result;
9 result.emplace(std::istreambuf_iterator<char>(in),
10 std::istreambuf_iterator<char>());
11 if (in.fail() && !in.eof()) {
12 result.reset();
13 }
14 return result;
15 }
16
17 int main(int argc, char** argv) {
18 if (argc <= 1) {return 1;}
19 auto s = read_file(argv[1]);
20 if (!s) {
21 std::cerr << "unable to read file\n";
22 return 1;
23 }
24 std::cout << *s;
25 }
Observers
Name Description
index returns zero-based index of alternative held
by variant
valueless_by_exception tests if variant in invalid state
Modifiers
Name Description
emplace constructs value in variant in place
swap swaps value with another variant
1 #include <variant>
2 #include <cassert>
3 #include <iostream>
4
5 int main() {
6 std::variant<int, double> x;
7 std::variant<int, double> y;
8 x = 2;
9 assert(std::get<int>(x) == std::get<0>(x));
10 assert(!x.valueless_by_exception());
11 y = 0.5;
12 assert(std::get<double>(y) == std::get<1>(y));
13 std::cout << std::get<int>(x) << ’\n’;
14 std::cout << std::get<double>(y) << ’\n’;
15 try {std::cout << std::get<double>(x) << ’\n’;}
16 catch (const std::bad_variant_access&) {
17 std::cout << "bad variant access\n";
18 }
19 }
Observers
Name Description
has_value tests if object holds value
type returns typeid of contained value
Modifiers
Name Description
emplace change contained object by constructing new
value in place
reset clear any contained object
swap swaps contents of two any objects
1 #include <any>
2 #include <cassert>
3 #include <string>
4 #include <iostream>
5
6 int main() {
7 std::any x{std::string("Hello")};
8 assert(x.has_value() && x.type() == typeid(std::string));
9 std::any y;
10 assert(!y.has_value());
11 x.swap(y);
12 assert(!x.has_value() && y.has_value());
13 x = y;
14 std::cout << std::any_cast<std::string>(x) << ’\n’;
15 y.reset();
16 assert(!y.has_value());
17 try {std::any_cast<int>(x);}
18 catch (const std::bad_any_cast&) {
19 std::cout << "any_cast failed\n";
20 }
21 }
Time Measurement
Clocks
Name Description
system_clock system clock (which may be adjusted)
steady_clock monotonic clock that ticks at constant rate
high_resolution_clock clock with shortest tick period available
1 #include <iostream>
2 #include <chrono>
3
4 // Get the granularity of a clock in seconds.
5 template <class C>
6 double granularity() {
7 return std::chrono::duration<double>(
8 typename C::duration(1)).count();
9 }
10
11 int main() {
12 std::cout << "system clock:\n" << "period "
13 << granularity<std::chrono::system_clock>() << ’\n’
14 << "steady "
15 << std::chrono::system_clock::is_steady << ’\n’;
16 std::cout << "high resolution clock:\n" << "period "
17 << granularity<std::chrono::high_resolution_clock>()
18 << ’\n’ << "steady "
19 << std::chrono::high_resolution_clock::is_steady << ’\n’;
20 std::cout << "steady clock:\n" << "period "
21 << granularity<std::chrono::steady_clock>() << ’\n’
22 << "steady "
23 << std::chrono::steady_clock::is_steady << ’\n’;
24 }
Miscellany
References
■ creating view does not perform any iteration (only creates object that can later be used
to perform iteration)
■ all iteration takes place when copy invoked
■ filter and transform perform processing (lazily) as range traversed
Concept Description
range begin returns iterator type and end returns sentinel type
input_range iterator type satisfies input_iterator concept (e.g.,
boost::range::istream_range)
output_range iterator type satisfies output_iterator concept
forward_range iterator type satisfies forward_iterator concept (e.g.,
forward_list, unordered_set, unordered_map,
unordered_multiset, and unordered_multimap)
bidirectional_range iterator type satisfies bidirectional_iterator concept
(e.g., list, set, map, multiset, and multimap)
random_access_range iterator type satisfies random_access_iterator concept
(e.g., deque)
contiguous_range iterator type satisfies contiguous_iterator concept and
data yields pointer to first element (e.g., built-in array type,
array, vector, and string)
Concept Description
common_range sentinel type same as iterator type (e.g., all standard library
containers at time of C++20)
sized_range range that provides amortized constant time size operation
view type is range that is view (i.e., has constant-time copy/-
move/assignment operations)
viewable_range range that can be safely converted into view
borrowed_range type is range whose iterators outlive range
input_range output_range
forward_range
bidirectional_range
random_access_range
contiguous_range
1 #include <cassert>
2 #include <ranges>
3 #include <string>
4 #include <vector>
5
6 int main() {
7 const std::vector<std::string> v{
8 "This", " ", "is", " ", "a", " ",
9 "very", " ", "very", " ", "very", " ", "very", " ",
10 "long", " ", "sentence", "."
11 };
12 std::string a;
13 for (auto&& i : v) {a += i;}
14 assert(a == "This is a very very very very long sentence.");
15 const auto first_very = std::find(v.begin(), v.end(), "very");
16 const auto sentence = std::find(v.begin(), v.end(), "sentence");
17 std::string s;
18 for (auto&& i : std::ranges::subrange(v.begin(), first_very))
19 {s += i;}
20 assert(s == "This is a ");
21 for (auto&& i : std::ranges::subrange(sentence, v.end()))
22 {s += i;}
23 assert(s == "This is a sentence.");
24 }
1 #include <type_traits>
2 #include <cstddef>
3 #include <ranges>
4
5 template<class T> requires std::is_object_v<T>
6 class empty_view : public std::ranges::view_interface<empty_view<T>> {
7 public:
8 static constexpr T* begin() noexcept {return nullptr;}
9 static constexpr T* end() noexcept {return nullptr;}
10 static constexpr T* data() noexcept {return nullptr;}
11 static constexpr std::size_t size() noexcept {return 0;}
12 static constexpr bool empty() noexcept {return true;}
13 };
1 #include <algorithm>
2 #include <cassert>
3 #include <ranges>
4 #include <sstream>
5 #include <vector>
6
7 int main() {
8 std::ranges::empty_view<int> ev;
9 assert(ev.begin() == ev.end());
10 std::ranges::single_view<int> sv{42};
11 assert(sv.size() == 1 && sv[0] == 42);
12 auto iv = std::ranges::iota_view(1, 4);
13 assert((std::ranges::equal(iv, std::vector{1, 2, 3})));
14 std::istringstream is("1 2 3 4 5");
15 std::vector<int> vi;
16 for (auto i : std::ranges::istream_view<int>(is))
17 {vi.push_back(i);}
18 assert((vi == std::vector{1, 2, 3, 4, 5}));
19 }
1 #include <cassert>
2 #include <cmath>
3 #include <ranges>
4 #include <vector>
5
6 int main() {
7 auto is_odd = [](int n) {return (n % 2) != 0;};
8 auto is_perfect_square = [](int n) {
9 int s = std::sqrt(n);
10 return s * s == n;
11 };
12 std::vector<int> x{7, 8, 9, 16, 25, 36, 81};
13 auto v = x |
14 std::ranges::views::filter(is_odd) |
15 std::ranges::views::filter(is_perfect_square) |
16 std::ranges::views::take(2) | std::views::common;
17 // take first two odd perfect squares from x
18 std::vector<int> result(v.begin(), v.end());
19 assert((result == std::vector<int>{9, 25}));
20 }
1 #include <cassert>
2 #include <cctype>
3 #include <iostream>
4 #include <ranges>
5 #include <string>
6
7 std::string trim_string(const std::string& s) {
8 auto is_space = [](char c) -> bool
9 {return std::isspace(static_cast<unsigned char>(c));};
10 auto trim_front = std::ranges::views::drop_while(is_space);
11 auto trim_back = std::ranges::views::reverse |
12 std::ranges::views::drop_while(is_space) |
13 std::ranges::views::reverse;
14 auto trim = trim_front | trim_back;
15 auto v = s | trim | std::views::common;
16 return std::string(v.begin(), v.end());
17 }
18
19 int main() {
20 assert(trim_string(" hi ") == "hi");
21 assert(trim_string(" bye ") == "bye");
22 }
1 #include <cctype>
2 #include <cassert>
3 #include <iostream>
4 #include <ranges>
5 #include <string>
6 #include <vector>
7
8 int main() {
9 std::vector<std::string> x{
10 "Hello", ",", " ", "World", "!", "\n"
11 };
12 auto to_upper = [](char c) -> char
13 {return std::toupper(static_cast<unsigned char>(c));};
14 auto v = x | std::views::join | std::views::transform(to_upper) |
15 std::views::common;
16 std::string s(v.begin(), v.end());
17 assert(s == "HELLO, WORLD!\n");
18 std::cout << s;
19 return !std::cout.flush();
20 }
1 #include <ranges>
2 #include <numeric>
3
4 constexpr int sum_of_squares(int n) {
5 auto square = [](int n) {return n * n;};
6 auto v = std::ranges::iota_view(1, n + 1) |
7 std::views::transform(square) |
8 std::views::common;
9 return std::accumulate(v.begin(), v.end(), 0);
10 }
11
12 static_assert(sum_of_squares(2) == 5);
13 static_assert(sum_of_squares(3) == 14);
1 #include <cassert>
2 #include <ranges>
3 #include <vector>
4
5 int main() {
6 std::vector<int> v{1, 2, 0, 3, 4, 5, 0, 6};
7 std::vector<std::vector<int>> result;
8 for (auto&& i : std::views::split(v, 0)) {
9 auto cv = std::ranges::common_view(i);
10 result.emplace_back(cv.begin(), cv.end());
11 }
12 std::vector<std::vector<int>> desired = {
13 {{1, 2}}, {{3, 4, 5}}, {{6}}
14 };
15 assert(result == desired);
16 }
1 #include <algorithm>
2 #include <cassert>
3 #include <ranges>
4
5 int main() {
6 std::vector v{3, 1, 4, 2};
7 std::ranges::sort(v);
8 assert((v == std::vector{1, 2, 3, 4}));
9 std::ranges::sort(v, std::greater{});
10 assert((v == std::vector{4, 3, 2, 1}));
11 }
1 #include <algorithm>
2 #include <iostream>
3 #include <ranges>
4 #include <vector>
5
6 struct Point {
7 int x;
8 int y;
9 };
10
11 std::ostream& operator<<(std::ostream& out, const Point& p)
12 {return out << ’(’ << p.x << ’,’ << p.y << ’)’;}
13
14 int main() {
15 std::vector<Point> p{{-1, 8}, {3, 7}, {-2, 6}, {0, 0}};
16 std::ranges::sort(p, {}, &Point::x);
17 for (auto&& i : p) {std::cout << i << ’\n’;}
18 std::cout << ’\n’;
19 std::ranges::sort(p, {}, &Point::y);
20 for (auto&& i : p) {std::cout << i << ’\n’;}
21 }
Text Formatting
1 #include <cassert>
2 #include <iomanip>
3 #include <iostream>
4 #include <sstream>
5 #include <string>
6
7 template <class... Args>
8 std::string format(const Args&... args) {
9 std::stringstream ss;
10 (ss << ... << args);
11 return ss.str();
12 }
13
14 struct widget {};
15 std::ostream& operator<<(std::ostream& out, const widget& w)
16 {return out << "hello";}
17
18 int main() {
19 std::string s = format(std::setw(4), std::setfill(’0’),
20 std::hex, 255);
21 assert(s == "00ff"); // OK
22 widget w;
23 s = format(std::setw(8), std::setfill(’*’), w);
24 assert(s == "***hello"); // OK
25 }
type safe and extensible but not efficient; only moderately readable
Name Description
format returns formatted representation of items as string
format_to writes formatted representation of items through
output iterator
format_to_n writes formatted representation of items through
output iterator without exceeding specified size
formatted_size returns number of characters in string resulting
from formatting items
vformat similar for format but items to be formatted spec-
ified as single argument
vformat_to similar for format_to but items to be formatted
specified as single argument
"Hello, World!"
2 std::format("{{{},{}}}", 1, 2) yields string "{1,2}"
"Hello, World!"
2 std::format("{1}, {0}!", "World", "Hello") yields
"Hello, World!"
2 std::format("{0:b} {0:o} {0:d} {0:x}", 10) yields
"1010 12 10 a"
2 std::format("{0}, {}!\n", "Hello", "World") is not valid and will
Examples
Format String and Items Formatting Result
"{0} {0:d}", 42 "42 42"
"{0:b} {0:#b} {0:#B}", 10 "1010 0b1010 0B1010"
"{0:c}", int(’A’) "A"
"{0:o} {0:#o} {1:#o}", 10, 0 "12 012 0"
"{0:x} {0:#x} {0:#X}", 42 "2a 0x2a 0X2A"
Examples
Format String and Items Formatting Result
"{0} {0:c}", ’a’ "a a"
"{0:b} {0:#b} {0:#B}", ’\n’ "1010 0b1010 0B1010" (for ASCII)
"{:d}", ’*’ "42" (for ASCII)
"{0:o} {0:#o}", ’\052’ "52 052"
"{0:x} {0:#x} {0:#X}", ’\x2a’ "2a 0x2a 0X2A"
Examples
Format String and Items Formatting Result
"{} {}", false, true "false true"
"{:s} {:s}", false, true "false true"
"{:#b} {:#B}", false, true "0b0 0B1"
"{:c}{:c}", false, true "\0\1"s
"{:d} {:d}", false, true "0 1"
"{:#o} {:#o}", false, true "0 01"
"{:#x} {:#X}", false, true "0x0 0X1"
Examples
Format String and Items Formatting Result
"{}", 16.75 "16.75"
"{0:a} {0:A}", 16.75 "0x1.0cp+4 0X1.0CP+4"
"{0:e} {0:E}", 16.75 "1.675000e+01 1.675000E+01"
"{0:f} {0:F}", 16.75 "16.750000 16.750000"
"{0:g} {0:G}", 16.75 "16.75 16.75"
Examples
Format String and Items Formatting Result
"{}", "hello" "hello"
"{:s}", "hello" "hello"
Sign Options
Option Description
+ use sign for both negative and nonnegative numbers
- use sign only for negative numbers
space use leading space for nonnegative numbers and minus sign for
negative numbers
none same as -
Examples
Format String and Items Formatting Result
"{0}|{0:-}|{0:+}|{0: }", 42 "42|42|+42| 42"
"{0}|{0:-}|{0:+}|{0: }", -42 "-42|-42|-42|-42"
"{0:}|{0:-}|{0:+}|{0: }", 1.5 "1.5|1.5|+1.5| 1.5"
"{0:x}|{0:-x}|{0:+x}|{0: x}", 42 "2a|2a|+2a| 2a"
Examples
Format String and Items Formatting Result
"{:4d}", 42 " 42"
"{:8}", "hello" "hello "
"{:{}}", "hello", 8 "hello "
"{:.4}", "goodbye" "good"
"{:.{}}", "goodbye", 4 "good"
"{:.1f}", 75.125 "75.1"
"{:10f}", 0.00390625 " 0.003906"†
"{:{}.{}f}", 16.125, 6, 1 " 16.1"
† precision defaults to 6
Examples
Format String and Items Formatting Result
"{:<9}", "hello" "hello "
"{:>9}", "hello" " hello"
"{:^9}", "hello" " hello "
"{:^8}", "hello" " hello "
"{:x^6}", 42 "xx42xx"
Examples
Locale Format String and Items Formatting Result
en_US.utf8 "{:L}", 1234567 "1,234,567"
en_DK.utf8 "{:L}", 1234567 "1.234.567"
en_US.utf8 "{:L}", 1.25 "1.25"
en_DK.utf8 "{:L}", 1.25 "1,25"
1 #include <cassert>
2 #include <format>
3 #include <iostream>
4 #include <locale>
5
6 #define approx_equal(x, y) \
7 {if ((x) != (y)) {std::cout << (x) << ’ ’ << (y) << ’\n’;}}
8
9 int main() {
10 std::locale us("en_US.utf8"); // English locale for USA
11 std::locale dk("en_DK.utf8"); // English locale for Denmark
12 assert(
13 std::format(us, "{:L}", 1234567890) == "1,234,567,890" &&
14 std::format(dk, "{:L}", 1234567890) == "1.234.567.890"
15 );
16 approx_equal(std::format(us, "{:.3L}", 1024.125), "1.02e+03");
17 approx_equal(std::format(dk, "{:.3L}", 1024.125), "1,02e+03");
18 }
1 #include <cassert>
2 #include <format>
3
4 int main() {
5 assert(
6 std::format("{:*^12}", "hello") == "***hello****" &&
7 std::format("{}", "hello") == "hello" &&
8 std::format("{:.4}", "goodbye") == "good" &&
9 std::format("{:8.4}", "goodbye") == "good "
10 );
11 }
1 #include <cassert>
2 #include <format>
3 #include <iostream>
4 #include <limits>
5
6 #define approx_equal(x, y) \
7 {if ((x) != (y)) {std::cout << (x) << ’ ’ << (y) << ’\n’;}}
8
9 int main() {
10 approx_equal(std::format("{}", 1.25), "1.25");
11 approx_equal(std::format("{0:a}|{0:A}", 7.75), "0x1.fp+2|0X1.FP+2");
12 approx_equal(std::format("{0:e}|{0:E}", 1’048’576.0625),
13 "1.048576e+06|1.048576E+06");
14 approx_equal(std::format("{0:f}|{0:F}", 1’048’576.0625),
15 "1048576.062500|1048576.062500")
16 approx_equal(std::format("{0:g}|{0:G}", 1’048’576.0625),
17 "1.04858e+06|1.04858E+06");
18 approx_equal(std::format("{:e}", 16.25), "1.625000e+01");
19 auto inf = std::numeric_limits<double>::infinity();
20 approx_equal(std::format("{0}|{0:+}|{1}", inf, -inf), "inf|+inf|-inf");
21 if constexpr(std::numeric_limits<double>::has_quiet_NaN) {
22 auto nan = std::numeric_limits<double>::quiet_NaN();
23 assert(std::format("{0}|{0:+}|{1}", nan, -nan) == "nan|+nan|-nan");
24 }
25 }
1 #include <format>
2 #include <iostream>
3
4 struct Point {int x; int y;};
5
6 template<> class std::formatter<Point> {
7 public:
8 std::format_parse_context::iterator
9 parse(std::format_parse_context& pc) {
10 auto i = pc.begin();
11 for (; i != pc.end() && *i != ’}’; ++i) {
12 if (*i == ’#’) {curly_ = true;}
13 else {throw std::format_error{"bad format specifier"};}
14 }
15 return i;
16 }
17 std::format_context::iterator
18 format(Point p, std::format_context& fc) {
19 return std::format_to(fc.out(), "{}{},{}{}",
20 (curly_ ? ’{’ : ’(’), p.x, p.y, (curly_ ? ’}’ : ’)’));
21 }
22 private:
23 bool curly_ = false;
24 };
1 #include <cassert>
2 #include <iostream>
3 #include <string>
4 #include "custom_1.hpp"
5
6 int main() try {
7 Point p{1, -2};
8 std::string s = std::format("{}", p);
9 assert(s == "(1,-2)");
10 s = std::format("{:#}", p);
11 assert(s == "{1,-2}");
12 s = std::format("{:#x}", p);
13 // ERROR: throws exception due to bad format specifier
14 } catch (std::format_error& e)
15 {std::cerr << e.what() << ’\n’;}
30 template<class T>
31 std::format_parse_context::iterator
32 std::formatter<my_range<T>>::parse(std::format_parse_context& pc) {
33 auto i = pc.begin();
34 for (; i != pc.end() && *i != ’}’; ++i) {}
35 efs_ = std::string(pc.begin(), i);
36 return i;
37 }
38
39 template<class T>
40 std::format_context::iterator
41 std::formatter<my_range<T>>::format(const my_range<T>& r,
42 std::format_context& fc) {
43 using namespace std::string_literals;
44 std::format_to(fc.out(), "[");
45 if (r.begin() != r.end()) {
46 auto i = r.begin();
47 std::string fs = "{"s + ":"s + efs_ + "}"s;
48 std::format_to(fc.out(), fs, *i);
49 ++i;
50 fs = " "s + fs;
51 for (; i != r.end(); ++i) {std::format_to(fc.out(), fs, *i);}
52 }
53 return std::format_to(fc.out(), "]");
54 }
1 #include <cassert>
2 #include <iostream>
3 #include <list>
4 #include <string>
5 #include <vector>
6 #include "custom_2.hpp"
7
8 int main() try {
9 const int x[] = {0xde, 0xad, 0xbe, 0xef};
10 std::string s = std::format("{:x}", my_range(std::views::all(x)));
11 assert(s == "[de ad be ef]");
12 const std::vector<int> v{0, 1, 2, 3, 4, 42};
13 s = std::format("{:b}", my_range(v));
14 assert(s == "[0 1 10 11 100 101010]");
15 s = std::format("{:x}", my_range(v));
16 assert(s == "[0 1 2 3 4 2a]");
17 const std::list<double> w{1.75, 42.24, 3.14};
18 s = std::format("{:6.1f}", my_range(w));
19 assert(s == "[ 1.8 42.2 3.1]");
20 s = std::format("{:Z}", my_range(v));
21 // ERROR: throws exception due to invalid type specifier
22 } catch (std::format_error& e)
23 {std::cerr << e.what() << ’\n’;}
Miscellany
■ Since C++ name lookup rules are quite complicated, we only present a
simplified (and therefore not fully correct) description of them here.
■ Qualified lookup. If the name A is preceded by the scope-resolution
operator, as in ::A or X::A, then use qualified name lookup.
2 In the first case, look in the global namespace for A. In the second case,
look up X, and then look inside it for A.
2 If X is a class and A is not a direct member, look in all of the direct bases of
X (and then each of their bases). If A is found in more than one base, fail.
■ Argument-dependent lookup. Otherwise, if the name is used as a
function call, such as A(X), use argument-dependent lookup.
2 Look for A in the namespace in which the type of X was declared, in the
1 #include <iostream>
2
3 namespace N {
4 struct W {};
5 void f(W x) {std::cout << "N::f\n";}
6 }
7
8 struct C {
9 void f(N::W x) {std::cout << "C::f\n";}
10 void g() {
11 N::W x;
12 f(x); // calls C::f (not N::f)
13 }
14 };
15
16 int main() {
17 C c;
18 c.g();
19 }
■ If the type T provides its own swap function, the name lookup on swap will
yield this function through ADL.
■ Otherwise, the name lookup will find std::swap.
■ Thus, code like the above will result in a more efficient swap function
being used if available, with the std::swap function used as a fallback.
More C++
Initialization
Heap
Stack
Environment and
High Address Program Arguments
Character Array
List Aggregate
Initialization
Initialization Initialization
From String Literal
Zero Default
Initialization Initialization
■ constant initialization can use all other types of initialization (i.e., zero, default,
value, direct, copy, list, and aggregate)
util_1.cpp
1 #include <vector>
2
3 std::vector<int> v{1, 2, 3, 4};
4 // invoked constructor is not constexpr; cannot use
5 // constant initialization; constructor invoked as
6 // part of dynamic initialization
util_2.cpp
1 #include <vector>
2 extern std::vector<int> v;
3
4 std::vector<int> w{v[0], v[1]};
5 // arguments for invoked constructor not constant
6 // expressions; cannot use constant initialization;
7 // constructor invoked as part of dynamic initialization;
8 // construction of w can invoke undefined behavior
9 // since v might not yet have been constructed
main.cpp
1 int main() {
2 // ...
3 }
1 #include <memory>
2 #include <string>
3
4 constinit std::unique_ptr<std::string> p1;
5 /* std::unique_ptr<std::string> not of literal type, but...
6 constructor being invoked is constexpr; p1 is constant
7 initialized */
8
9 constinit std::unique_ptr<std::string> p2(nullptr);
10 /* std::unique_ptr<std::string> not of literal type, but...
11 constructor being invoked is constexpr and argument to
12 constructor is constant expression; p2 is constant
13 initialized */
14
15 constinit std::unique_ptr<std::string> q1;
16 /* std::shared_ptr<std::string> not of literal type, but...
17 constructor being invoked is constexpr; q1 is constant
18 initialized */
19
20 constinit std::shared_ptr<std::string> q2(nullptr);
21 /* std::shared_ptr<std::string> not of literal type, but...
22 constructor being invoked is constexpr and argument to
23 constructor is constant expression; q2 is constant
24 initialized */
util_1.cpp
1 #include "constant_initialization_2_util.hpp"
2 constexpr double x{1};
3 constexpr double y{2};
4 constinit std::complex<double> z0{x, y};
5 // invoked constructor is constexpr; all arguments to
6 // constructor are constant expressions; as part of static
7 // initialization, z0 is constant initialized to (1, 2)
util_2.cpp
1 #include "constant_initialization_2_util.hpp"
2 std::complex<double> z1 = z0 - std::complex<double>{0, 2};
3 // as part of dynamic initialization, z1 is copy
4 // initialized to (1, 2) - (0, 2) = (1, 0); static
5 // initialization of z0 guaranteed to have been already
6 // performed
main.cpp
1 int main() {/* ... */}
literal 0 to T
2 if T is class type, each of following is zero initialized and any padding
1 #include <string>
2
3 struct Point {int x; int y;};
4
5 static int ga[2]; /* ga is statically zero initialized
6 (to {0, 0}) 1 */
7 static int gb[2] = {1}; /* gb[1] is statically zero initialized
8 (to 0) as part of constant initializing gb to {1, 0} 1 */
9 char *gp; /* gp is statically zero initialized (to null
10 pointer) 1 */
11 std::string gs; /* gs is statically zero initialized
12 (to indeterminate value) and (later) dynamically
13 default initialized to empty string 1 */
14 int gi; // gi is statically zero initialized (to 0)
15
16 int main() {
17 char buf[4] = "hi"; /* buf[3] is zero initialized (to 0) as
18 part of initializing buf to {’h’, ’i’, ’\0’, 0} 2 */
19 static float f; /* f is statically zero initialized
20 (to 0.0f) 1 */
21 int i{}; /* i is zero initialized (to 0) as part of
22 list initialization 3 */
23 const Point& p = Point(); /* referenced object is
24 zero initialized (to {0, 0}) as part of
25 value initialization 3 */
26 }
Note: n indicates case n from earlier slide
against empty argument list and called constructor provides initial value for
new object
2 if T is array type, each element of array is default initialized
2 otherwise, nothing is done (which results in indeterminate value in case of
object with automatic storage duration)
1 #include <string>
2
3 struct Widget {
4 Widget() {} /* w is default initialized to indeterminate
5 value 3 */
6 int w;
7 };
8
9 static std::string gs; /* gs is (statically) zero initialized
10 and then (dynamically) default initialized to
11 empty string 1 */
12
13 int main() {
14 std::string s; /* s is default initialized to
15 empty string 1 */
16 std::string* sp = new std::string; /* heap-allocated object is
17 default initialized to empty string 2 */
18 int i; /* i is default initialized to indeterminate
19 value 1 */
20 int* ip = new int; /* heap-allocated object is
21 default initialized to indeterminate value 2 */
22 }
1 #include <string>
2 #include <vector>
3
4 struct Widget {
5 Widget() : s("hi") {} // s is direct initialized to "hi" 5
6 explicit Widget(const std::string& s_) : s(s_) {}
7 std::string s;
8 std::string t{"bye"}; /* t is direct initialized to "bye" via
9 direct-list initialization 1 */
10 };
11
12 int main() {
13 std::vector<int> u(2, 42); // u is direct initialized to {42, 42} 1
14 std::vector<int> v(3); // v is direct initialized to {0, 0, 0} 1
15 std::string s("bye"); // s is direct initialized to "bye" 1
16 int i(1); // i is direct initialized to 1 1
17 int j{1}; /* j is direct initialized to 1 as part of
18 direct-list initialization */
19 double d = static_cast<double>(i); /* temporary object is
20 direct initialized to 1.0 3 */
21 std::string* sp = new std::string("hi"); /* heap-allocated object is
22 direct initialized to "hi" 2 */
23 [s](){return s.size();}(); /* s data member in closure is
24 direct initialized to value of s in main 5 */
25 Widget w = Widget("hi"); /* temporary object is direct initialized
26 to {"hi", "bye"} 4 */
27 }
1 #include <string>
2 using namespace std::literals;
3
4 struct Widget {
5 std::string s = "hi"; // s is copy initialized to "hi" 1
6 };
7
8 std::string identity(std::string p) {
9 return p; // return value copy initialized from p 3
10 }
11
12 int main() {
13 std::string a[2] = {"hi", "bye"}; /*
14 as part of aggregate initialization:
15 a[0] is copy initialized to "hi" and
16 a[1] is copy initialized to "bye" 6 */
17 std::string s = "hello"s; // s is copy initialized to "hello" 1
18 std::string t = {3, ’A’}; /* t is copy initialized to "AAA" as
19 part of copy-list initialization 1 */
20 s = identity(s); // function parameter is copy initialized from s 2
21 try {
22 throw t; // exception object copy initialized from t 4
23 } catch (std::string s) {
24 // s is copy initialized from exception object 5
25 }
26 if (auto i = s.begin(); i != s.end()) {/* ... */}
27 // i is copy initialized from s.begin() 1
28 }
2 if T is class type with default constructor and braced initializer list empty, object is
value initialized ⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §11.6.4/(3.4)]
2 if T is specialization of std::initializer_list, object is direct initialized or copy
initialized, depending on context, from braced initializer list ⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §11.6.4/(3.5)]
2 if T is class type, constructors of T considered in two phases (first, using
constructors that can be called with std::initializer_list as single argument;
then using all constructors) [C++17 §11.6.4/(3.6)]
⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓
2 if T is enumeration type, if braced initializer list has only one initializer (and some
other constraints satisfied), enumeration initialized with result of converting initializer
to enumeration’s underlying type [C++17 §11.6.4/(3.7)]
⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓
2 if T is not class type and braced initializer list has exactly one element and either T is
not reference type or is reference type that is compatible with type of element, object
is direct initialized (for direct-list initialization) or copy initialized (for copy-list
initialization) ⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §11.6.4/(3.8)]
2 if T is reference type that is not compatible with type of element, temporary of
referenced type is list initialized and reference bound to temporary [C++17 §11.6.4/(3.9)]
⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓
2 otherwise, if braced initializer list empty, object is value initialized [C++17 §11.6.4/(3.10)]
⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓
1 #include <initializer_list>
2 #include <vector>
3
4 struct Widget {
5 Widget() : w{1, 2, 3} {} // w is direct-list initialized 5
6 Widget(std::initializer_list<int> w_) : w{w_} {} /* w is
7 direct initialized as part of direct-list initialization 5 */
8 std::vector<int> v{3, 2, 1}; // v is direct-list initialized 4
9 std::vector<int> w;
10 };
11
12 int main() {
13 Widget w1{1, 2, 3}; // w1 is direct-list initialized 1
14 const Widget& w2 = Widget{1, 2, 3};
15 // temporary object is direct-list initialized 2
16 Widget* w3 = new Widget{1, 2, 3};
17 // heap-allocated object is direct-list initialized 3
18 for (auto&& i : {1, 2, 3}) {}
19 // temporary object is direct-list initialized 2
20 }
1 #include <vector>
2 #include <tuple>
3 #include <initializer_list>
4
5 struct Widget {
6 Widget() : v({1, 2, 3}) {}
7 // constructor argument is copy-list initialized 2
8 Widget(std::initializer_list<int> v_) : v{v_} {}
9 const int& operator[] (std::pair<int, int> i) const
10 {return i.first ? v[i.second] : w[i.second];}
11 std::vector<int> v;
12 std::vector<int> w = {3, 2, 1}; // w is copy-list initialized 7
13 };
14
15 Widget func(Widget w) {
16 return {1, 2, 3}; // returned value is copy-list initialized 3
17 }
18
19 int main() {
20 Widget w = {1, 2, 3}; // w is copy-list initialized 1
21 w = {1, 2, 3}; // temporary object is copy-list initialized 5
22 func({1, 2, 3}); // function argument is copy-list initialized 2
23 Widget({1, 2, 3}); // constructor argument is copy-list initialized 6
24 int i = w[{0, 1}];
25 // operator[] function parameter is copy-list initialized 4
26 }
■ special rule employed for initializing character array from string literal
[C++17 §11.6/17.3]
⁓⁓⁓⁓⁓⁓⁓⁓⁓
■ array element type and character type of string literal must be compatible
■ each character in string literal (including null-terminator) placed in order
into successive array elements ⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §11.6.2]
1 int main() {
2 char a1[] = "hi";
3 // initialized to {’h’, ’i’, ’\0’} from string literal
4 char a2[] = {"hi"}; /* initialized to {’h’, ’i’, ’\0’} as
5 part of copy-list initialization */
6 char a3[]{"hi"};
7 char a4[3]{"hi"};
8 /* each of a3 and a4 is initialized to {’h’, ’i’, ’\0’}
9 as part of direct-list initialization */
10
11 char b1[4] = "hi";
12 char b2[4] = {"hi"};
13 char b3[4]{"hi"};
14 /* each of b1, b2, and b3 is initialized to
15 {’h’, ’i’, ’\0’, ’\0’} */
16
17 char16_t c[] = u"hi"; /* initialized to
18 {u’h’, u’i’, u’\0’} from string literal */
19 char32_t d[] = U"hi"; /* initialized to
20 {U’h’, U’i’, U’\0’} from string literal */
21 wchar_t e[4]{L"hi"}; /* initialized to
22 {L’h’, L’i’, L’\0’, L’\0’} as part of
23 direct-list initialization */
24 }
1 struct Gadget {
2 int x;
3 int y;
4 };
5
6 struct Widget {
7 Gadget g;
8 int i;
9 };
10
11 int main() {
12 int x[2][2] = {1, 2, 3, 4};
13 // effectively initializer is {{1, 2}, {3, 4}}
14 Widget v = {1, 2, 3};
15 // effectively initializer is {{1, 2}, 3}
16 Widget w = {1, 2};
17 // effectively initializer is {{1, 2}}
18 // w initialized to {{1, 2}, 0}
19 }
1 #include <initializer_list>
2
3 auto i1 = 42; // type of i1 deduced as int
4 auto i2(42); // type of i2 deduced as int
5 auto i3{42}; // type of i3 deduced as int
6 // auto i4{42, 42}; // ERROR: exactly one element required
7 auto i5 = {42};
8 // type of i5 deduced as std::initializer_list<int>
1 #include <initializer_list>
2
3 struct Widget {
4 Widget();
5 Widget(std::initializer_list<int>);
6 Widget(int);
7 };
8
9 int main() {
10 Widget w{};
11 // invokes default constructor; for empty
12 // braced initializer list, default constructor
13 // preferred over std::initializer_list constructor
14 Widget v{42};
15 // invokes constructor taking std::initializer_list;
16 // for non-empty braced initializer list, constructor
17 // taking std::initializer_list preferred over those
18 // that do not
19 }
1 #include <map>
2 #include <vector>
3 #include <string>
4
5 std::map<int, std::string> m{
6 {42, "forty two"},
7 {0, "zero"}
8 }; // initialized to map with two elements
9
10 std::vector<std::string> v1{"hi", "bye"};
11 // initialized to vector with two elements
12
13 std::vector<std::string> v2{{"hi", "bye"}};
14 // ERROR: will try to initialize to vector with
15 // one element; invokes std::string constructor that
16 // takes two iterators as parameters; pointers
17 // to "hi" and "bye" passed as begin and end
18 // iterators; this results in undefined behavior
1 struct Widget {
2 Widget(int i_) : i(i_) {}
3 int i;
4 };
5
6 int main() {
7 Widget v(42.0);
8 // OK: narrowing conversion allowed in
9 // direct initialization
10 // Widget w{42.0};
11 // ERROR: narrowing conversion not allowed in
12 // list initialization
13 }
1 #include <vector>
2 #include <string>
3
4 std::vector<int> v1(3, 42);
5 // initialized to vector with elements 42, 42, 42
6 std::vector<int> v2{3, 42};
7 // initialized to vector with elements 3, 42
8
9 std::string s1(3, ’a’);
10 // initialized to string consisting of 3 ’a’ characters
11 std::string s2{3, ’a’};
12 // initialized to string consisting of characters ’\3’, ’a’
1 #include <iostream>
2 #include <initializer_list>
3
4 auto f(int a, int b, int c) {
5 return std::initializer_list<int>{a, b, c};
6 // ERROR: initializer_list references elements in
7 // temporary array whose lifetime need not extend
8 // beyond lifetime of initializer_list;
9 // therefore, returned initializer_list
10 // likely references invalid data
11 }
12
13 int main() {
14 // nothing good likely to happen here
15 for (auto i : f(1, 2, 3)) {
16 std::cout << i << ’\n’;
17 }
18 }
References
Temporary Objects
■ Argument passing:
double func(const double& x);
func(3); // must create temporary
// double _tmp = 3;
// func(_tmp);
■ Reference initialization:
int i = 2;
const double& d = i; // must create temporary
// double _tmp = i;
// const double& d = _tmp;
■ Function return:
std::string getMessage();
std::string s;
s = getMessage(); // must create temporary
// std::string _tmp(getMessage());
// s = _tmp;
Original code:
int main() {
Complex a(1.0, 2.0);
Complex b(1.0, 1.0);
Complex c;
// ...
c = a + b;
}
1 #include <complex>
2 using std::complex;
3
4 int main() {
5 complex<double> a(1.0, 1.0);
6 complex<double> b(1.0, -1.0);
7 complex<double> z(0.0, 0.0);
8
9 // 2 temporary objects
10 // 2 constructors, 2 destructors
11 // 1 operator=, 1 operator+, 1 operator*
12 z = b * (z + a);
13
14 // no temporary objects
15 // only 1 operator+= and 1 operator*=
16 z += a;
17 z *= b;
18 }
■ Example:
void func() {
std::string s1("Hello");
std::string s2(" ");
std::string s3("World!\n");
const std::string& s = s1 + s2 + s3;
std::cout << s; // OK?
}
■ Example:
const std::string& getString() {
return std::string("Hello");
}
void func() {
std::cout << getString(); // OK?
}
expression
glvalue
rvalue
int& get_value();
++get_value(); // get_value() is an lvalue
■ A string literal is an lvalue. [C++17 §8.1.1/1]
⁓⁓⁓⁓⁓⁓⁓⁓
Example:
const char *s = "Hello"; // "Hello" is an lvalue
■ A named rvalue reference is an lvalue. [C++17 §8/7]
⁓⁓⁓⁓⁓⁓
Example:
int&& i = 1 + 3;
int j = i; // i is an lvalue
void func();
void (&&f)() = func;
f(); // f is an lvalue
std::move(func)(); // std::move(func) is an lvalue
■ The result of each of the following built-in operators is an lvalue:
2 built-in subscripting operator (except when array rvalue involved) [C++17 §8.2.1/1]
⁓⁓⁓⁓⁓⁓⁓⁓
2 built-in indirection operator [C++17 §8.3.1/1]
⁓⁓⁓⁓⁓⁓⁓⁓
2 built-in pre-increment and pre-decrement operators ⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §8.3.2/1] [C++17 §8.3.2/2]
⁓⁓⁓⁓⁓⁓⁓⁓
2 built-in assignment and compound-assignment operators [C++17 ⁓⁓⁓⁓⁓⁓⁓⁓
§8.18/1]
Example:
char buffer[] = "Hello";
char* s = buffer;
*s = ’a’; // *s is an lvalue
*(s + 1) = ’b’; // *(s + 1) is an lvalue
++s; // ++s is an lvalue
--s; // --s is an lvalue
s += 2; // s += 2 is an lvalue
s = &buffer[1];
// s = &buffer[1] is an lvalue
// buffer[1] is an lvalue
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1004
Moving and Lvalues
■ The result of calling a function that returns by value (i.e., the return type is
not a reference type) is a prvalue. [C++17 §8.2.2/11] Example:
⁓⁓⁓⁓⁓⁓⁓⁓⁓
int get_value();
int i = get_value();
// get_value() is a prvalue
// Note: get_value() is not the same as get_value
■ All literals other than string literals are prvalues. [C++17 §8.1.1/1]
⁓⁓⁓⁓⁓⁓⁓⁓
Example:
double pi = 3.1415; // 3.1415 is a prvalue
int i = 42; // 42 is a prvalue
i = 2 * i + 1; // 2 and 1 are prvalues
char c = ’A’; // ’A’ is a prvalue
■ The this keyword is a prvalue. [C++17 §8.1.2/2]
⁓⁓⁓⁓⁓⁓⁓⁓
Example:
int i;
int j;
i = -(3 + 5); // 3 + 5 and -(3 + 5) are prvalues
j = i * i; // i * i is a prvalue
j = (i == 42); // i == 42 is a prvalue
j = (i & 7) | 2; // (i & 7) and (i & 7) | 2 are prvalues
i = j++; // j++ is a prvalue
int *ip = &i; // &i is a prvalue
Example:
std::vector<int> v;
v = std::vector<int>(10, 2);
// temporary object materialized from prvalue
// std::vector<int>(10, 2) is an xvalue
std::complex<double> u;
u = std::complex<double>(1, 2);
// temporary object materialized from prvalue
// std::complex<double>(1, 2) is an xvalue
■ Class rvalues can have cv-qualified types, while non-class rvalues always
have cv-unqualified types. ⁓⁓⁓⁓⁓⁓
[C++17 §8/6] Example:
■ With regard to propagating the value from one object to another, we can
summarize the results from the earlier slides as follows:
2 If the source for a copy operation is an lvalue, the copy operation is not
■ Aside from the exceptions noted below, all of the built-in operators require
operands that are prvalues. ⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §8.2.1/1] [C++17 §8.2.5/2]
⁓⁓⁓⁓⁓⁓⁓⁓
■ The operand of each of the following built-in operators must be an lvalue:
2 address of [C++17 §8.3.1/3]
⁓⁓⁓⁓⁓⁓⁓⁓
2 pre- and post-increment [C++17 §8.2.6/1] [C++17 §8.3.2/1]
⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓
2 pre- and post-decrement ⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §8.2.6/1] [C++17 §8.3.2/1]
⁓⁓⁓⁓⁓⁓⁓⁓
■ The left operand of the following built-in operators must be an lvalue:
2 assignment [C++17 §8.18/1]
⁓⁓⁓⁓⁓⁓⁓
2 compound assignment [C++17 §8.18/1]
⁓⁓⁓⁓⁓⁓⁓
■ Whether an operator for a class type requires operands that are lvalues or
rvalues or yield lvalues or rvalues is determined by the parameter types
and return type of the operator function.
■ The member selection operator may yield an lvalue or rvalue, depending
on the particular manner in which the operator is used. (The behavior is
fairly intuitive.) ⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §8.2.5/4]
■ The value category and type of the result produced by the ternary
conditional operator depends on the particular manner in which the
operator is employed. [C++17 §8.16/5] [C++17 §8.16/6]
⁓⁓⁓⁓⁓⁓⁓ ⁓⁓⁓⁓⁓⁓⁓⁓
int i = 1;
int j = 2;
int k = i + j;
/* since built-in binary addition operator requires
prvalue operands, i and j implicitly converted from
lvalues to prvalues */
■ not only were two copy/move operations eliminated, need for any
temporary objects also eliminated
callee
2 caller invokes callee
callee
2 caller invokes callee
callee
2 caller invokes callee
■ in function call, when temporary class object not bound to reference would
be copied/moved to class object with same cv-unqualified type, temporary
object can be constructed directly in target of omitted copy/move
[C++20 §9.4/15][C++20 §8.2.2/7]
⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓
■ copy elision always required if allowed (as per above) [C++20 §9.4/(17.6.1)]
⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓
■ example:
1 struct Widget {
2 Widget();
3 Widget(const Widget&);
4 Widget(Widget&&);
5 // ...
6 };
7
8 void func(Widget w) {/* ... */}
9
10 int main() {
11 func(Widget()); // required copy elision
12 func(std::move(Widget())); /* BAD IDEA:
13 copy elision not allowed; move performed */
14 }
■ by using copy elision, not only was one copy/move operation eliminated,
but temporary object also eliminated
default constructor)
2 storage for callee’s function parameter p allocated (on stack)
3 value of temporary object in caller propagated (via move/copy
constructor) to callee’s function parameter p
4 caller transfers control to callee
5 callee returns, resulting in its function parameter being destroyed (and
deallocated)
6 temporary object in caller destroyed (and deallocated)
■ overhead: one temporary object created (constructor and destructor
invocations); one move/copy required to propagate value from temporary
object elsewhere
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1030
Pass-By-Value Example: With Copy Elision
deallocated)
■ no overhead: no temporary objects created and therefore no need to
propagate values into or out of temporary objects
■ example:
1 struct Widget {
2 Widget();
3 Widget(const Widget &);
4 Widget(Widget&&);
5 // ...
6 };
7
8 void func_1(){
9 throw Widget(); // required copy elision (prvalue)
10 }
11
12 void func_2(){
13 Widget w; throw w; // possible copy elision (not prvalue)
14 }
1 class Widget {
2 public:
3 Widget() {/* ... */}
4 // not copyable
5 Widget(const Widget&) = delete;
6 Widget& operator=(const Widget&) = delete;
7 // not movable
8 Widget(Widget&&) = delete;
9 Widget& operator=(Widget&&) = delete;
10 // ...
11 };
12
13 Widget make_widget() {
14 return Widget();
15 }
16
17 int main() {
18 Widget w(make_widget());
19 // OK: copy elision required
20 Widget v{Widget()};
21 // OK: copy elision required
22 Widget u(Widget());
23 // function declaration
24 }
[C++20 §11.10.5/(3.1)]
⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓
■ overload resolution to select constructor for copy (or return_value
overload to call) is first performed as if return expression were rvalue
■ if overload resolution fails or was not performed, overload resolution is
performed again, considering return expression as lvalue
65 void func_6(Widget w) {
66 // copy elision is not allowed since object in throw expression is function
67 // parameter; since return expression w is implicitly movable and function
68 // parameter, w first treated as rvalue, resulting in move being performed
69 // (as of C++20)
70 throw w;
71 }
72
73 Widget func_7(Widget&& w) {
74 // copy elision is not allowed for several reasons (e.g., cv-unqualified
75 // return type does not match cv-unqualified return-expression type, not
76 // automatic object); since return expression w is implicitly movable and
77 // function parameter, w first treated as rvalue, resulting in move being
78 // performed (as of C++20)
79 return w;
80 }
81
82 Widget func_8(Widget& w) {
83 Widget&& x = std::move(w);
84 // copy elision is not allowed for several reasons (e.g., cv-unqualified
85 // return type does not match cv-unqualified return-expression type and not
86 // automatic object); since return expression x is implicitly movable and
87 // declared in function body, x first treated as rvalue, resulting in move
88 // being performed (as of C++20)
89 return x;
90 }
1 class Widget {
2 public:
3 Widget();
4 Widget(const Widget&);
5 Widget(Widget&&);
6 // ...
7 };
8
9 class Gadget {
10 public:
11 Gadget();
12 Gadget(const Gadget&);
13 Gadget(Gadget&&);
14 Gadget(const Widget& w); // copying converting constructor
15 Gadget(Widget&& w); // moving converting constructor
16 // ...
17 };
18
19 class Doodad {
20 public:
21 Doodad();
22 Doodad(const Doodad&);
23 Doodad(Doodad&&);
24 operator Widget() const&; // copying conversion operator
25 operator Widget() &&; // moving conversion operator
26 // ...
27 };
29 Gadget func_1() {
30 Widget w;
31 // copy elision is not permitted since cv-unqualified return type and
32 // cv-unqualified return-expression type do not match; since return
33 // expression w is implicitly movable and declared in function body, w
34 // first treated as rvalue, resulting in move being performed via moving
35 // converting constructor
36 return w;
37 }
38
39 Widget func_2() {
40 Doodad t;
41 // copy elision is not permitted since cv-unqualified return type does not
42 // match cv-unqualified return-expression type; since return expression t
43 // is implicitly movable and declared in function body, t first treated as
44 // rvalue, resulting in move being performed via moving conversion operator
45 // (as of C++20)
46 return t;
47 }
1 class BigInt {
2 public:
3 BigInt();
4 BigInt(const BigInt&);
5 BigInt(BigInt&&);
6 BigInt& operator=(const BigInt&);
7 BigInt& operator=(BigInt&&);
8 BigInt& operator+=(int);
9 BigInt& operator++();
10 // ...
11 };
12
13 BigInt func_1(BigInt c) {
14 c += 1;
15 // copy elision for return value is not allowed since return expression is
16 // function parameter; since return expression c is implicitly movable and
17 // declared in function parameter list, c first treated as rvalue,
18 // resulting in move being performed
19 return c;
20 }
22 BigInt func_2(BigInt c) {
23 // copy elision for return value is not allowed since cv-unqualified
24 // return-expression type does not match cv-unqualified return type;
25 // since return expression is not implicitly movable, copy is performed
26 return c += 1;
27 }
28
29 BigInt func_3(const BigInt& c) {
30 // copy elision for return value not allowed for several reasons (e.g.,
31 // function parameter, not automatic, cv-unqualified type mismatch); since
32 // return expression is not implicitly movable, copy is performed
33 return BigInt(c) += 1;
34 }
1 class Widget {
2 public:
3 Widget();
4 Widget(const Widget&);
5 Widget(Widget&&);
6 // ...
7 };
8
9 Widget func_0() {
10 Widget w;
11 // copy elision is permitted
12 // if not elided, move is performed
13 return w;
14 }
15
16 Widget func_1() {
17 Widget w;
18 // standard seems to suggest copy elision is not permitted since
19 // return expression (w) is not name of object, but:
20 // Clang 11.0.1 (-std=c++20 -O2) elides copy;
21 // MSVC 19.28 (/std:c++20 /O2) elides copy;
22 // GCC 10.2 (-std=c++20 -O3) performs move
23 return (w);
24 }
Rvalue References
Introduction
■ A type that includes one or both of the qualifiers const and volatile
is called a cv-qualified type.
■ A type that is not cv-qualified is called cv-unqualified. [C++17 §6.9.3/1]
⁓⁓⁓⁓⁓⁓⁓⁓
■ Example:
The types const int and volatile char are cv-qualified.
The types int and char are cv-unqualified.
■ An object or function that is named by an identifier is said to be named.
■ An object or function that cannot be referred to by name is said to be
unnamed.
■ Example:
std::vector<int> v = {1, 2, 3, 4};
std::vector<int> w;
w = v; // w and v are named
w = std::vector<int>(2, 0);
// w is named
// std::vector<int>(2, 0) is unnamed
■ Suppose that we have two objects of the same type and we want to
propagate the value of one object (i.e., the source) to the other object (i.e.,
the destination).
■ This can be accomplished in one of two ways:
1 copying; or
2 moving.
■ Copying propagates the value of the source object to the destination
object without modifying the source object.
■ Moving propagates the value of the source object to the destination
object and is permitted to modify the source object.
■ Moving is always at least as efficient as copying, and for many types,
moving is more efficient than copying.
■ For some types, copying does not make sense, while moving does (e.g.,
std::ostream and std::istream).
src dst
data_ s0 data_ s0
size_ n s1 size_ n s1
.. ..
. .
sn−1 sn−1
src dst
data_ s0 data_ d0
size_ m s1 size_ n d1
.. ..
. .
sn−1 dm−1
■ Moving is usually more efficient than copying, often by very large margin.
■ So, we should prefer moving to copying.
■ We can safely replace a copy by a move when subsequent code does not
depend on the value of source object.
■ It would be convenient if the language could provide a mechanism for
automatically using a move (instead of a copy) in situations where doing
so is always guaranteed to be safe.
■ For reasons of efficiency, it would also be desirable for the language to
provide a mechanism whereby the programmer can override the normal
behavior and force a move (instead of a copy) in situations where such a
transformation is known to be safe only due to some special additional
knowledge about program behavior.
■ Rvalue references (in concert with the rules for reference binding and
overload resolution) provide the above mechanisms.
■ The kinds of expressions, to which lvalue and rvalue references can bind,
differ.
■ For a nonreference type T (such as int or const int), what kinds of
expressions can validly be placed in each of the boxes in the example
below?
T& r = ;
T&& r = ;
■ Lvalue and rvalue references also behave differently with respect to
overload resolution.
■ Let T be a cv-unqualified nonreference type. Which overloads of func will
be called in the example below?
T operator+(const T&, const T&);
void func(const T&);
void func(T&&);
T x;
func(x); // calls which version of func?
func(x + x); // calls which version of func?
■ Again, the loss of cv qualifiers must be avoided for const and volatile
correctness.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1063
Reference Binding (Continued)
■ If rvalue references could bind to lvalues, the above guarantee could not
be made, as an rvalue reference could then refer to an object whose value
cannot be changed safely, namely, an lvalue.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1065
Why Non-Const Lvalue References Cannot Bind to Rvalues
Rvalue Lvalue
const const
const volatile const volatile
T volatile T volatile
T T T T
T T
T&& ✓ C V C,V ✗ ✗ ✗ ✗
const
✓ ✓ V V ✗ ✗ ✗ ✗
T&&
volatile
✓ ✗ ✓ C ✗ ✗ ✗ ✗
T&&
const
volatile ✓ ✓ ✓ ✓ ✗ ✗ ✗ ✗
T&&
T& ✗ ✗ ✗ ✗ ✓ C V C,V
const T& ✓ ✓ V V ✓ ✓ V V
volatile
✗ ✗ ✗ ✗ ✓ C ✓ C
T&
const
volatile ✗ ✗ ✗ ✗ ✓ ✓ ✓ ✓
T&
✓: allowed C: strips const V: strips volatile ✗: other
Priority
Rvalue Lvalue
const const
const volatile const volatile
T volatile T volatile
T T T T
T T
T&& 1
const
2 1
T&&
volatile
2 1
T&&
const
volatile 3 2 2 1
T&&
T& 1
const T& 4 3 2 1
volatile
2 1
T&
const
volatile 3 2 2 1
T&
1 #include <iostream>
2 #include <string>
3
4 void func(std::string& x) {
5 std::cout << "func(std::string&) called\n";
6 }
7
8 void func(const std::string& x) {
9 std::cout << "func(const std::string&) called\n";
10 }
11
12 void func(std::string&& x) {
13 std::cout << "func(std::string&&) called\n";
14 }
15
16 void func(const std::string&& x) {
17 std::cout << "func(const std::string&&) called\n";
18 }
19
20 const std::string&& constValue(const std::string&& x) {
21 return static_cast<const std::string&&>(x);
22 }
23
24 int main() {
25 const std::string cs("hello");
26 std::string s("world");
27 func(s);
28 func(cs);
29 func(cs + s);
30 func(constValue(cs + s));
31 }
32
33 /* Output:
34 func(std::string&) called
35 func(const std::string&) called
36 func(std::string&&) called
37 func(const std::string&&) called
38 */
1 #include <iostream>
2 #include <string>
3
4 void func(const std::string& x) {
5 std::cout << "func(const std::string&) called\n";
6 }
7
8 void func(std::string&& x) {
9 std::cout << "func(std::string&&) called\n";
10 }
11
12 const std::string&& constValue(const std::string&& x) {
13 return static_cast<const std::string&&>(x);
14 }
15
16 int main() {
17 const std::string cs("hello");
18 std::string s("world");
19 func(s);
20 func(cs);
21 func(cs + s);
22 func(constValue(cs + s));
23 }
24
25 /* Output:
26 func(const std::string&) called
27 func(const std::string&) called
28 func(std::string&&) called
29 func(const std::string&) called
30 */
Moving
■ Example:
class T {
public:
T();
T(const T&); // copy constructor
T(T&&); // move constructor
// ...
};
T a;
T b(std::move(a)); // calls T::T(T&&)
T c(b); // calls T::T(const T&)
■ Recall the class from earlier that represents a character buffer (whose
size is fixed at run time).
class Buffer {
public:
// ...
private:
char* data_; // pointer to buffer data
std::size_t size_; // buffer size (in characters)
};
1 #include <algorithm>
2 #include <cstddef>
3
4 class Buffer {
5 public:
6 Buffer(std::size_t size, char value = 0) :
7 size_(size), data_(new char[size])
8 {std::fill_n(data_, size, value);}
9 Buffer(const Buffer& b) : size_(b.size_), data_(new char[b.size_])
10 {std::copy_n(b.data_, b.size_, data_);}
11 Buffer& operator=(const Buffer& b) {
12 if (this != &b) {
13 delete[] data_;
14 size_ = b.size_; data_ = new char[b.size_];
15 std::copy_n(b.data_, b.size_, data_);
16 }
17 return *this;
18 }
19 ~Buffer() {delete[] data_;}
20 private:
21 char* data_; // pointer to buffer data
22 std::size_t size_; // buffer size (in characters)
23 };
24
25 Buffer getBuffer() {return Buffer(65536, ’A’);}
26
27 int main() {
28 Buffer x(0);
29 Buffer y = getBuffer(); // construct from temporary object
30 x = Buffer(32768, ’B’); // assign from temporary object
31 }
■ In the above code, a swap requires three copy operations (namely, one
copy constructor call and two copy assignment operator calls).
■ For many types T, this use of copying is very inefficient.
■ Furthermore, the above code requires that T must be copyable (i.e., T has
a copy constructor and copy assignment operator).
■ In C++11, we can write a much better swap function.
1 #include <iostream>
2
3 class Widget {
4 public:
5 void func() const &
6 {std::cout << "const lvalue\n";}
7 void func() &
8 {std::cout << "non-const lvalue\n";}
9 void func() const &&
10 {std::cout << "const rvalue\n";}
11 void func() &&
12 {std::cout << "non-const rvalue\n";}
13 };
14
15 const Widget getConstWidget() {return Widget();}
16
17 int main(){
18 Widget w;
19 const Widget cw;
20 w.func(); // non-const lvalue
21 cw.func(); // const lvalue
22 Widget().func(); // non-const rvalue
23 getConstWidget().func(); // const rvalue
24 }
1 class Int {
2 public:
3 Int(int x = 0) : value_(x) {}
4 // only allow prefix increment for lvalues
5 Int& operator++() & {++value_; return *this;}
6 // The following allows prefix increment for rvalues:
7 // Int& operator++() {++value_; return *this;}
8 // ...
9 private:
10 int value_;
11 };
12
13 int one() {return 1;}
14
15 int main() {
16 int i = 0;
17 int j = ++i; // OK
18 // int k = ++one(); // ERROR (not lvalue)
19 Int x(0);
20 Int y = ++x; // OK
21 // Int z = ++Int(1); // ERROR (not lvalue)
22 }
1 #include <iostream>
2 #include <vector>
3 #include <utility>
4
5 class Buffer {
6 public:
7 Buffer(char value = 0) : data_(1024, value) {}
8 void data(std::vector<char>& x) const &
9 {x = data_;}
10 void data(std::vector<char>& x) &&
11 {x = std::move(data_);}
12 // ...
13 private:
14 std::vector<char> data_;
15 };
16
17 Buffer getBuffer() {return Buffer(42);}
18
19 int main() {
20 std::vector<char> d;
21 Buffer buffer;
22 buffer.data(d); // copy into d
23 getBuffer().data(d); // move into d
24 }
int i = 0;
int& & j = i; // ILLEGAL: reference to reference
■ Although one cannot directly create a reference to a reference, a
reference to a reference can arise indirectly in several contexts.
■ Typedef name:
typedef int& RefToInt;
typedef RefToInt& T; // reference to reference
■ Template function parameters:
template <class T> T func(const T& x) {return x;}
int x = 1;
func<int&>(x); // reference to reference
■ Decltype specifier:
int i = 1;
decltype((i))& j = i; // reference to reference
■ Auto specifier:
int i = 0;
auto&& j = i; // reference to reference
■ Class templates:
template <class T>
struct Thing {
void func(T&&) {} // reference to reference
// if T is reference type
};
Thing<int&> x;
■ In other words:
2 An lvalue reference to any reference yields an lvalue reference.
2 An rvalue reference to an lvalue reference yields an lvalue reference.
2 An rvalue reference to an rvalue reference yields rvalue reference.
2 Any cv qualifiers applied to a reference type are discarded (since cv
qualifiers cannot be applied to a reference).
E&&.
■ Thus, the type T&& will be an lvalue reference type if expr is an lvalue, and
an rvalue reference type if expr is an rvalue.
■ Therefore, the lvalue/rvalue-ness of expr can be determined inside f
based on whether T&& is an lvalue reference type or rvalue reference type.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1093
Forwarding References Example
1 #include <utility>
2
3 template <class T> void f(T&& p);
4 int main() {
5 int i = 42;
6 const int ci = i;
7 const int& rci = i;
8 f(i);
9 // i is lvalue with type int
10 // T is int&
11 // p has type int&
12 f(ci);
13 // ci is lvalue with type const int
14 // T is const int&
15 // p has type const int&
16 f(rci);
17 // rci is lvalue with type const int&
18 // T is const int&
19 // p has type const int&
20 f(2);
21 // 2 is rvalue with type int
22 // T is int
23 // p has type int&&
24 f(std::move(i));
25 // std::move(i) is rvalue with type int&&
26 // T is int
27 // p has type int&&
28 }
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1094
Section 3.5.6
Perfect Forwarding
References
Exceptions
Introduction
High-Level
main
Code
Low-Level
Code
1 #include <iostream>
2 #include <vector>
3 #include <utility>
4
5 std::pair<bool, int> safe_divide(int x, int y) {
6 if (!y) {
7 return std::pair(false, 0);
8 }
9 return std::pair(true, x / y);
10 }
11
12 int main() {
13 std::vector<std::pair<int, int>> v = {{10, 2}, {10, 0}};
14 for (auto p : v) {
15 auto result = safe_divide(p.first, p.second);
16 if (result.first) {
17 int quotient = result.second;
18 std::cout << quotient << ’\n’;
19 } else {
20 std::cerr << "division by zero\n";
21 }
22 }
23 }
1 #include <iostream>
2 #include <stdexcept>
3 #include <utility>
4 #include <vector>
5
6 int safe_divide(int x, int y) {
7 if (!y) {
8 throw std::overflow_error("divide by zero");
9 }
10 return x / y;
11 }
12
13 int main() {
14 std::vector<std::pair<int, int>> v = {{10, 2}, {10, 0}};
15 for (auto p : v) {
16 try {
17 std::cout << safe_divide(p.first, p.second) <<
18 ’\n’;
19 }
20 catch(const std::overflow_error& e) {
21 std::cerr << "division by zero\n";
22 }
23 }
24 }
■ advantages of exceptions:
2 exceptions allow for error handling code to be easily separated from code
that detects error
2 exceptions can easily pass error information many levels up call chain
2 passing of error information up call chain managed by language (no explicit
code required)
■ disadvantages of exceptions:
2 writing code that always behaves correctly in presence of exceptions
requires great care (as we shall see)
2 although possible to have no execution-time cost when exceptions not
thrown, still have memory cost (to store information needed for stack
unwinding for case when exception is thrown)
Exceptions
1 #include <iostream>
2
3 class Base {};
4 class Derived : public Base {};
5
6 void func(Base& x) {
7 throw x; // always throws Base
8 }
9
10 int main() {
11 Derived d;
12 try {func(d);}
13 catch (Derived& e) {
14 std::cout << "Derived\n";
15 }
16 catch (...) {
17 std::cout << "not Derived\n";
18 }
19 }
1 #include <iostream>
2
3 class Base {
4 public:
5 virtual void raise() {throw *this;}
6 };
7 class Derived : public Base {
8 public:
9 virtual void raise() {throw *this;}
10 };
11
12 void func(Base& x) {
13 x.raise();
14 }
15
16 int main() {
17 Derived d;
18 try {func(d);}
19 catch (Derived& e) {
20 std::cout << "Derived\n";
21 }
22 catch (...) {
23 std::cout << "not Derived\n";
24 }
25 }
1 void handle_exception() {
2 try {throw;}
3 catch (const exception_1& e) {
4 log_error("exception_1 occurred");
5 // ...
6 }
7 catch (const exception_2& e) {
8 log_error("exception_2 occurred");
9 // ...
10 }
11 // ...
12 }
13
14 void func() {
15 try {operation();}
16 catch (...) {handle_exception();}
17 // ...
18 try {another_operation();}
19 catch (...) {handle_exception();}
20 }
■ during stack unwinding, destructors called in order for second, first, hello,
world, and bye (i.e., reverse order of construction); dave unaffected
1 #include <string>
2 #include <iostream>
3
4 struct Base {
5 Base() {}
6 ~Base() {};
7 };
8
9 class Widget : public Base {
10 public:
11 Widget() {}
12 ~Widget() {}
13 // ...
14 private:
15 std::string s_;
16 std::string t_;
17 };
18
19 int main() {
20 Widget w;
21 // ...
22 }
1 #include <iostream>
2 #include <stdexcept>
3
4 class Gadget {
5 public:
6 Gadget() {throw std::runtime_error("ctor");}
7 ~Gadget() {}
8 };
9
10 class Widget {
11 public:
12 // constructor uses function try block
13 Widget()
14 try {std::cerr << "ctor body\n";}
15 catch (...) {std::cerr << "exception in ctor\n";}
16 ~Widget() {std::cerr << "dtor body\n";}
17 private:
18 Gadget g_;
19 };
20
21 int main()
22 try {Widget w;}
23 catch (...) {
24 std::cerr << "terminating due to exception\n";
25 return 1;
26 }
1 #include <iostream>
2 #include <stdexcept>
3
4 struct Gadget {
5 Gadget(bool b) {
6 if (b) {throw std::runtime_error("yikes");}
7 }
8 };
9
10 struct Widget : public Gadget {
11 Widget(bool b) try : Gadget(b) {}
12 catch(const std::exception& e) {
13 std::cerr << e.what() << ’\n’;
14 // exception automatically rethrown
15 }
16 };
17
18 int main() try {
19 Widget v(true);
20 } catch (const std::exception& e) {
21 std::cerr << e.what() << ’\n’;
22 }
1 #include <iostream>
2 #include <stdexcept>
3
4 struct Gadget {
5 ~Gadget() noexcept(false)
6 {throw std::runtime_error("yikes");}
7 };
8
9 class Widget : public Gadget {
10 public:
11 ~Widget() try {}
12 catch(const std::exception& e) {
13 std::cerr << e.what() << ’\n’;
14 // exception automatically rethrown
15 }
16 };
17
18 int main() try {
19 Widget w;
20 } catch (const std::exception& e) {
21 std::cerr << e.what() << ’\n’;
22 }
1 #include <iostream>
2 #include <stdexcept>
3
4 int func(int x) try {
5 throw std::runtime_error("whatever");
6 } catch(const std::exception& e) {
7 std::cerr << e.what() << ’\n’;
8 return x;
9 // exception not automatically rethrown
10 // function does not emit exception
11 }
12
13 int main() {
14 std::cout << func(42) << ’\n’;
15 }
Exception Specifications
2 ensure that function invoked in manner such that copy elision is guaranteed
■ might want to store exception and then later retrieve and rethrow it
■ exception can be stored using std::exception_ptr type
■ current exception can be retrieved with std::current_exception
■ rethrow exception stored in exception_ptr object using
std::rethrow_exception
■ provides mechanism for moving exceptions between threads:
2 store exception on one thread
2 then retrieve and rethrow stored exception on another thread
■ std::make_exception_ptr can be used to make exception_ptr
object
1 #include <exception>
2 #include <stdexcept>
3
4 void yikes() {
5 throw std::runtime_error("Yikes!");
6 }
7
8 std::exception_ptr getException() {
9 try {
10 yikes();
11 }
12 catch (...) {
13 return std::current_exception();
14 }
15 return nullptr;
16 }
17
18 int main() {
19 std::exception_ptr e = getException();
20 std::rethrow_exception(e);
21 }
Exception Safety
being satisfied (e.g., for std::vector, element type has nonthrowing move
or is copyable)
2 insert on std::list
■ examples of nothrow guarantee:
2 swap of two containers
1 #include <iostream>
2 #include <ios>
3 #include <boost/io/ios_state.hpp>
4
5 // not exception safe
6 void unsafeOutput(std::ostream& out, unsigned int x) {
7 auto flags = out.flags();
8 // if exception thrown during output of x, old
9 // formatting flags will not be restored
10 out << std::hex << std::showbase << x << ’\n’;
11 out.flags(flags);
12 }
13
14 // exception safe
15 void safeOutput(std::ostream& out, unsigned int x) {
16 boost::io::ios_flags_saver ifs(out);
17 out << std::hex << std::showbase << x << ’\n’;
18 }
Exception Gotchas
2 call func
■ each of T1 and T2 objects managed by shared_ptr at all times so no
memory leak possible if exception thrown
■ similar issue arises in context of std::unique_ptr and can be resolved
by using std::make_unique in similar way as above
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1181
Stack Example
References
1 Jon Kalb. Exception-Safe Code. CppCon, Bellevue, WA, USA, Sep 7–12,
2014. Available online at https://youtu.be/W7fIy_54y-w,
https://youtu.be/b9xMIKb1jMk, and
https://youtu.be/MiKxfdkMJW8. (This talk is in three parts.)
2 Jon Kalb. Exception-Safe Coding. C++Now, Aspen, CO, USA, May
13–18, 2012. Available online at https://youtu.be/N9bR0ztmmEQ and
https://youtu.be/UiZfODgB-Oc. (This talk is in two parts.)
3 Peter Edwards. C++ Exception Handling — The Gory Details of an
Implementation. Dublin C/C++ User Group Meetup, Feb. 2018. Available
online at https://youtu.be/XpRL7exdFL8.
Smart Pointers
Introduction
■ reasonable implementation would have zero memory cost for deleter state
in case of:
2 default deleter
2 deleter of functor/closure type with no state
■ if no memory cost for deleter state, unique_ptr has same memory cost
as raw pointer
Modifiers
Member Name Description
release returns pointer to managed object and releases ownership
reset replaces managed object
swap swaps managed objects
Observers
Member Name Description
get returns pointer to managed object
get_deleter returns deleter used for destruction of managed object
operator bool checks if there is associated managed object
Dereferencing/Subscripting
Member Name Description
operator* dereferences pointer to managed object
operator-> dereferences pointer to managed object
operator[] provides indexed access to managed array
1 #include <memory>
2 #include <cassert>
3
4 void func() {
5 auto p1(std::make_unique<int>(42));
6 assert(*p1 == 42);
7
8 // std::unique_ptr<int> p3(p1); // ERROR: not copyable
9 // p3 = p1; // ERROR: not copyable
10
11 std::unique_ptr<int> p2(std::move(p1)); // OK: movable
12 // Transfers ownership from p1 to p2, invalidating p1.
13 assert(p1.get() == nullptr && *p2 == 42);
14
15 p1 = std::move(p2); // OK: movable
16 // Transfers ownership from p2 to p1, invalidating p2.
17 assert(p2.get() == nullptr && *p1 == 42);
18
19 p1.reset();
20 // Invalidates p1.
21 assert(p1.get() == nullptr);
22 }
1 #include <memory>
2 #include <cassert>
3
4 int main() {
5 auto p0 = std::make_unique<int>(0);
6 assert(*p0 == 0);
7 int* r0 = p0.get();
8 auto p1 = std::make_unique<int>(1);
9 assert(*p1 == 1);
10 auto r1 = p1.get();
11 p0.swap(p1);
12 assert(p0.get() == r1 && p1.get() == r0);
13 p1.swap(p0);
14 assert(p0.get() == r0 && p1.get() == r1);
15 p1.reset();
16 assert(p1.get() == nullptr);
17 assert(!p1);
18 int* ip = p1.release();
19 assert(!p1);
20 // ... Do not throw exceptions here.
21 delete ip;
22 p1.reset(new int(42));
23 assert(*p1 == 42);
24 }
1 #include <cstddef>
2 #include <limits>
3 #include <memory>
4
5 class TwoBufs {
6 public:
7 TwoBufs(std::size_t aSize, std::size_t bSize) :
8 a_(std::make_unique<char[]>(aSize)),
9 b_(std::make_unique<char[]>(bSize)) {}
10 ~TwoBufs() {}
11 // ...
12 private:
13 std::unique_ptr<char[]> a_;
14 std::unique_ptr<char[]> b_;
15 };
16
17 void doWork() {
18 // This will not leak memory.
19 TwoBufs x(1000000,
20 std::numeric_limits<std::size_t>::max());
21 }
shared_ptr<T>
Pointer to T Object
Pointer to Control Block
Control Block
Pointer to Managed Object Managed Object
..
Use Count (1) .
Weak Count (1) T Object
..
Other Data .
shared_ptr<T>
Pointer to T Object
Pointer to Control Block
shared_ptr<T>
Pointer to T Object
Pointer to Control Block
Control Block
Pointer to Managed Object Managed Object
..
Use Count (2) .
Weak Count (1) T Object
..
Other Data .
shared_ptr<T>
Pointer to T Object
Pointer to Control Block
shared_ptr<T>
Pointer to T Object
Pointer to Control Block
shared_ptr<T>
Pointer to T Object
Pointer to Control Block
Control Block
Pointer to Managed Object Managed Object
..
Use Count (3) .
Weak Count (1) T Object
..
Other Data .
Modifiers
Member Name Description
reset replaces managed object
swap swaps values of two shared_ptr objects
Observers
Member Name Description
get returns pointer to pointed-to object
use_count returns number of shared_ptr objects referring
to same managed object
operator bool checks if there is associated pointed-to/managed
object
owner_before provides owner-based ordering of shared pointers
Dereferencing/Subscripting
Member Name Description
operator* dereferences pointer to pointed-to object
operator-> dereferences pointer to pointed-to object
operator[] provides indexed access to pointed-to array
1 #include <memory>
2 #include <cassert>
3
4 int main() {
5 auto p1(std::make_shared<int>(0));
6 assert(*p1 == 0 && p1.use_count() == 1);
7
8 std::shared_ptr<int> p2(p1);
9 assert(*p2 == 0 && p2.use_count() == 2);
10
11 *p2 = 42;
12 assert(*p1 == 42);
13
14 p2.reset();
15 assert(!p2);
16 assert(*p1 == 42 && p1.use_count() == 1);
17
18 int* ip = p1.get();
19 assert(*ip == 42);;
20
21 ip = p2.get();
22 assert(ip == nullptr);
23 }
1 #include <memory>
2 #include <iostream>
3 #include <string>
4
5 int main() {
6 std::shared_ptr<std::string> s =
7 std::make_shared<std::string>("hello");
8
9 std::shared_ptr<const std::string> cs = s;
10
11 *s = "goodbye";
12
13 // *cs = "bonjour"; // ERROR: const
14
15 std::cout << *cs.get() << ’\n’;
16 }
wp
p v
cb i
p
rc = 1
15 auto wp(std::make_shared<Widget>(
16 std::vector<int>{1, 2, 3}, 42));
17 assert(wp.use_count() == 1);
18 assert(wp->i == 42 && wp->v.size() == 3);
wp vp
p v p
cb i cb
p
rc = 2
wp vp
p = 0 v p
cb = 0 i cb
p
rc = 1
wp vp
p = 0 p = 0
cb = 0 cb = 0
1 #include <memory>
2 #include <cassert>
3
4 // Aside: This is an example of the CRTP.
5 class Widget : public std::enable_shared_from_this<Widget>
6 {
7 public:
8 std::shared_ptr<Widget> getSharedPtr() {
9 return shared_from_this();
10 }
11 std::shared_ptr<const Widget> getSharedPtr() const {
12 return shared_from_this();
13 }
14 // ...
15 };
16
17 int main() {
18 std::shared_ptr<Widget> a(new Widget);
19 std::shared_ptr<Widget> b = a->getSharedPtr();
20 assert(b == a);
21 std::shared_ptr<const Widget> c = a->getSharedPtr();
22 assert(c == a);
23 }
1 #include <memory>
2 #include <array>
3 #include <string>
4 #include <iostream>
5
6 using namespace std::literals;
7
8 int main() {
9 std::array<std::shared_ptr<const std::string>, 3> all = {
10 std::make_shared<const std::string>("apple"s),
11 std::make_shared<const std::string>("orange"s),
12 std::make_shared<const std::string>("banana"s)
13 };
14 std::array<std::shared_ptr<const std::string>, 2> some =
15 {all[0], all[1]};
16
17 for (auto& x : all) {
18 std::cout << *x << ’ ’ << x.use_count() << ’\n’;
19 }
20 }
21
22 /* output:
23 apple 2
24 orange 2
25 banana 1
26 */
apple
p
some
rc = 2
all .. p
.
p cb
cb p
orange
p cb
cb p
rc = 2
p ..
.
cb
banana
p
rc = 1
..
.
root
p
p p
cb
cb
l p
cb
r p
cb
i = 1
p
rc = 1
root
p
p p p p
cb
cb cb
l p l p
cb cb
r p r p
cb cb
i = 1 i = 2
p p
rc = 1 rc = 1
■ create new node, making it left child of root node (parent link not set)
root
p
p p p p
cb
cb cb
l p l p
cb cb
r p r p
cb cb
i = 1 i = 2
p p
rc = 2 rc = 1
p p p p
cb cb
l p l p
cb cb
r p r p
cb cb
i = 1 i = 2
p p
rc = 1 rc = 1
Modifiers
Member Name Description
reset releases reference to managed object
swap swaps values of two weak_ptr objects
Observers
Member Name Description
use_count returns number of shared_ptr objects referring to
same managed object
expired checks if referenced object was already deleted
lock creates shared_ptr that manages referenced object
owner_before provides owner-based ordering of weak pointers
root
p
p p
cb
cb
l p
cb
r p
cb
i = 1
p
rc = 1
wc = 1
root
p
p p p p
cb
cb cb
l p l p
cb cb
r p r p
cb cb
i = 1 i = 2
p p
rc = 1 rc = 1
wc = 1 wc = 1
■ created new node, making it left child of root node (parent link not set)
root
p
p p p p
cb
cb cb
l p l p
cb cb
r p r p
cb cb
i = 1 i = 2
p p
rc = 1 rc = 1
wc = 2 wc = 1
■ set parent link (which is weak_ptr) for left child of root node
root
p
p p p p
cb
cb cb
l p l p
cb cb
r p r p
cb cb
i = 1 i = 2
p p
rc = 0 rc = 1
wc = 2 wc = 1
root
p
p p p p
cb
cb cb
l p l p
cb cb
r p r p
cb cb
i = 1 i = 2
p p
rc = 0 rc = 1
wc = 2 wc = 1
root
p
p p p p
cb
cb cb
l p l p
cb cb
r p r p
cb cb
i = 1 i = 2
p p
rc = 0 rc = 0
wc = 2 wc = 1
■ started to destroy l (in root node); decremented use count, which reaches zero
root
p
p p p p
cb
cb cb
l p l p
cb cb
r p r p
cb cb
i = 1 i = 2
p p
rc = 0 rc = 0
wc = 2 wc = 1
root
p
p p p p
cb
cb cb
l p l p
cb cb
r p r p
cb cb
i = 1 i = 2
p p
rc = 0 rc = 0
wc = 1 wc = 1
■ started to destroy p in left node; decremented weak count (which is not yet zero)
root
p
p p p p
cb
cb cb
l p l p
cb cb
r p r p
cb cb
i = 1 i = 2
p p
rc = 0 rc = 0
wc = 1 wc = 1
root
p
p p
cb
cb
l p
cb
r p
cb
i = 1
p p
rc = 0 rc = 0
wc = 1 wc = 1
root
p
p p
cb
cb
l p
cb
r p
cb
i = 1
p p
rc = 0 rc = 0
wc = 1 wc = 0
root
p
p p
cb
cb
l p
cb
r p
cb
i = 1
p
rc = 0
wc = 1
■ destroyed control block for (previously destroyed) left child of root node
root
p
p p
cb
cb
l p
cb
r p
cb
i = 1
p
rc = 0
wc = 1
root
p
cb
p
rc = 0
wc = 1
root
p
cb
p
rc = 0
wc = 0
root
p
cb
root
p
cb
1 #include <memory>
2
3 void func() {
4 // ...
5 int size = /* ... */;
6 auto buffer(std::make_unique<char[]>(size));
7 // ... (use buffer)
8 // when buffer destroyed, pointee automatically
9 // freed
10 }
■ instead of making object member of class, store object outside class and
make pointer to object member of class
■ might want to do this for object that:
2 is optional (e.g., is not always used or is lazily initialized)
2 has one of several base/derived types
■ pointer in class object owns decoupled object
1 #include <memory>
2
3 class Widget {
4 // ...
5 private:
6 // ...
7 std::unique_ptr<Type> item_;
8 // decoupled object has type Type
9 };
■ array stored outside class object, where array size fixed but determined at
run time
■ class object has pointer that owns decoupled array
1 #include <memory>
2
3 class Widget {
4 public:
5 using Element = int;
6 Widget(std::size_t size) :
7 array_(std::make_unique(Element[]>(size),
8 size_(size) {}
9 // ...
10 private:
11 // ...
12 std::unique_ptr<Element[]> array_;
13 std::size_t size_;
14 };
■ with .pimpl idiom, interface and implementation split across two classes,
. . ...........
namely, handle class and implementation class
■ handle object has pointer that owns implementation object
1 #include <experimental/propagate_const>
2 #include <memory>
3
4 class Widget {
5 public:
6 // ... (member functions that forward calls to
7 // implementation object)
8 private:
9 class WidgetImpl; // implementation class defined elsewhere
10 std::experimental::propagate_const<std::unique_ptr<
11 WidgetImpl>> impl_;
12 // incomplete type WidgetImpl is allowed
13 // ...
14 };
■ tree, where tree owns root node and each node owns its children
■ recursive destruction of nodes may cause stack-overflow problems,
especially for unbalanced trees (but such problems can be avoided by
dismantling tree from bottom upwards)
1 #include <memory>
2 #include <array>
3
4 class Tree {
5 public:
6 class Node {
7 // ...
8 private:
9 std::array<std::unique_ptr<Node>, 2> children_;
10 // owning pointers (parent owns children)
11 Node* parent_; // non-owning pointer
12 // ...
13 };
14 // ...
15 private:
16 std::unique_ptr<Node> root_;
17 // ...
18 };
■ doubly-linked list, where list owns first list node and each list node owns its
successor
■ recursive destruction of nodes can cause stack-overflow problems, for
sufficiently large lists (but deep recursions can be avoided with extra work)
1 #include <memory>
2
3 class List {
4 public:
5 class Node {
6 // ...
7 private:
8 std::unique_ptr<Node> next_;
9 // owning pointer (node owns successor)
10 Node* prev_; // non-owning pointer
11 };
12 // ...
13 private:
14 // ...
15 std::unique_ptr<Node> head_;
16 };
1 #include <memory>
2 #include <array>
3
4 class Tree {
5 public:
6 using Data = /* ... */;
7 class Node {
8 // ...
9 private:
10 std::array<std::shared_ptr<Node>, 2> children_;
11 std::weak_ptr<Node> parent_;
12 Data data_;
13 };
14 std::shared_ptr<Data> find(/* ... */) {
15 std::shared_ptr<Node> sp;
16 // ...
17 return {sp, &(sp->data)};
18 // use shared_ptr aliasing constructor
19 }
20 private:
21 std::shared_ptr<Node> root_;
22 };
1 #include <memory>
2 #include <vector>
3
4 class Dag {
5 public:
6 class Node {
7 // ...
8 private:
9 std::vector<std::shared_ptr<Node>> children_;
10 // owning pointers
11 std::vector<Node*> parents_; // non-owning pointers
12 // ...
13 };
14 private:
15 std::vector<std::shared_ptr<Node>> roots_; // owning pointers
16 };
1 #include <memory>
2
3 std::unique_ptr<Widget> makeWidget() {
4 return std::make_unique<Widget>();
5 }
6
7 std::shared_ptr<Gadget> makeGadget() {
8 return std::make_shared<Gadget>();
9 }
1 #include <memory>
2
3 std::shared_ptr<Widget> makeWidget(int id) {
4 static std::map<int, std::weak_ptr<Widget>> cache;
5 static std::mutex mut;
6 std::scoped_lock<std::mutex> lock(mut);
7 auto sp = cache[id].lock();
8 if (!sp) {
9 sp = std::make_shared<Widget>(id);
10 cache[id] = sp;
11 }
12 return sp;
13 }
References
Memory Management
■ leaked object: object created but not destroyed when no longer needed
■ leaked objects are problematic because can cause program to waste
memory or exhaust all available memory
■ premature deletion (a.k.a. dangling references): object is deleted when
one or more references to object still exist
■ premature deletion is problematic because, if object accessed after
deletion, results of doing so will be unpredictable (e.g., read garbage
value or overwrite other variables in program)
■ double deletion: object is deleted twice, invoking destructor twice
■ double deletion is problematic invoking destructor on nonexistent object is
unpredictable and furthermore double deletion can often corrupt data
structures used by memory allocator
■ type can have restriction on address at which objects of that type can
start, called alignment requirement [C++17 §6.11/1]
⁓⁓⁓⁓⁓⁓⁓⁓
■ for given object type T, starting address for objects of type T must be
integer multiple of N bytes, where integer N is called alignment of type
■ alignment of 1 corresponds to no restriction on alignment (since starting
address of object can be any address in memory)
■ alignment of 2 restricts starting address of object to be even (i.e., integer
multiple of 2)
■ for efficiency reasons and due to restrictions imposed by hardware,
alignment of particular type may be greater than 1
■ for fundamental type T, not uncommon for alignment of T to equal
sizeof(T)
1 #include <new>
2 #include <cassert>
3 #include <string>
4
5 void func_1() {
6 // allocating array operator new
7 std::string* sp = static_cast<std::string*>(
8 ::operator new[](1000 * sizeof(std::string)));
9 // allocation succeeded since no exception thrown
10 assert(sp);
11 // ... (deallocate)
12 }
13
14 void func_2() {
15 std::string* sp = static_cast<std::string*>(
16 ::operator new[](1000 * sizeof(std::string), std::nothrow));
17 // sp may be null since allocation might have failed
18 // ... (deallocate)
19 }
20
21 void func_3() {
22 static int a[1000];
23 int* ip = static_cast<int*>(::operator new[](1000 * sizeof(int),
24 static_cast<void*>(a)));
25 assert(ip == a);
26 }
1 #include <new>
2 #include <cassert>
3 #include <string>
4
5 void func_1() {
6 // allocating operator new
7 std::string* sp = static_cast<std::string*>(
8 ::operator new(sizeof(std::string)));
9 // allocation succeeded since no exception thrown
10 assert(sp);
11 ::operator delete(sp);
12 }
13
14 void func_2() {
15 // allocating and non-throwing operator new
16 std::string* sp = static_cast<std::string*>(
17 ::operator new(sizeof(std::string), std::nothrow));
18 // sp may be null since allocation might have failed
19 // deleting null pointer is allowed
20 ::operator delete(sp);
21 }
1 #include <new>
2 #include <cassert>
3 #include <string>
4
5 void func_1() {
6 // allocating array operator new
7 std::string* sp = static_cast<std::string*>(
8 ::operator new[](1000 * sizeof(std::string)));
9 // allocation succeeded since no exception thrown
10 assert(sp);
11 ::operator delete[](sp);
12 }
13
14 void func_2() {
15 std::string* sp = static_cast<std::string*>(
16 ::operator new[](1000 * sizeof(std::string), std::nothrow));
17 // sp may be null since allocation might have failed
18 // deleting null pointer is allowed
19 ::operator delete[](sp);
20 }
1 #include <cstddef>
2 #include <new>
3
4 // Gadget does not have extended alignment
5 struct Gadget {
6 int i;
7 };
8
9 // Widget has extended alignment
10 struct alignas(2 * alignof(std::max_align_t)) Widget {
11 int i;
12 };
13
14 int main() {
15 Gadget* gp = new Gadget;
16 // invokes operator new(std::size_t)
17 delete gp;
18 // invokes operator delete(void *)
19 Widget* wp = new Widget;
20 // invokes operator new(std::size_t, std::align_val_t)
21 delete wp;
22 // invokes operator delete(void *, std::align_val_t)
23 }
■ some global versions of single-object and array operator new and operator
delete can be replaced
■ to replace function, define in single translation unit
■ undefined behavior if more than one replacement provided in program or if
replacement defined with inline specifier
1 #include <cstdlib>
2 #include <cstdio>
3 #include <stdexcept>
4
5 class Widget {
6 public:
7 void *operator new(std::size_t size) {
8 std::printf("Widget::operator new\n");
9 if (void *p = std::malloc(size); !p) {throw std::bad_alloc();}
10 else {return p;}
11 }
12 void operator delete(void* p) noexcept {
13 std::printf("Widget::operator delete\n");
14 std::free(p);
15 }
16 // ...
17 };
18
19 int main() {
20 Widget* wp = new Widget; // invokes Widget::operator new
21 delete wp; // invokes Widget::operator delete
22 Widget* vp = ::new Widget; // invokes global operator new
23 ::delete vp; // invokes global operator delete
24 }
1 #include <cstdint>
2
3 // heap-allocated array of bounded size
4 template <class T>
5 class bvec {
6 public:
7 // create empty vector that can hold max_size elements
8 // why is this implementation extremely inefficient?
9 bvec(std::size_t max_size) {
10 start_ = new T[max_size];
11 end_ = start_ + max_size;
12 finish_ = start_; // mark array empty
13 }
14 // why is this implementation extremely inefficient?
15 ~bvec() {
16 delete[] start_;
17 }
18 // ...
19 private:
20 T* start_; // start of storage for element data
21 T* finish_; // one past end of element data
22 T* end_; // end of storage for element data
23 };
bvec<T> Array of T
x0
start_
x1
finish_ ..
.
end_ xn−1
xn
xn+1
..
.
xm−1
1 #include <iostream>
2 #include <cassert>
3 #include <memory>
4
5 // class that overloads address-of operator
6 class Foo {
7 public:
8 Foo(int i) : i_(i) {}
9 const Foo* operator&() const {return nullptr;}
10 Foo* operator&() {return nullptr;}
11 int get() const {return i_;}
12 // ...
13 private:
14 int i_;
15 };
16
17 int main() {
18 Foo f(42);
19 assert(&f == nullptr);
20 assert(std::addressof(f) != nullptr &&
21 std::addressof(f)->get() == 42);
22 std::cout << std::addressof(f) << ’\n’;
23 }
■ consider container class template called optval that can hold optional
value
■ class templated on type T of optional value
■ container object in one of two states:
1 holding value of type T
optval<T>
valid_
storage_
1 #include <cassert>
2 #include <string>
3 #include <iostream>
4 #include "optional_1_util.hpp"
5
6 int main() {
7 optval<std::string> s;
8 assert(!s.has_value());
9 s.set("Hello, World");
10 assert(s.has_value());
11 std::cout << s.get() << ’\n’;
12 s.clear();
13 assert(!s.has_value());
14 }
array<T, N>
finish_
buf_ x0
x1
..
.
xs−1
xs
xs+1
..
.
xN−1
vec<T> Array of T
x0
start_
x1
finish_ ..
.
end_ xn−1
xn
xn+1
..
.
xm−1
Allocators
unordered_multimap
■ all container class templates in standard library that take allocator as
parameter use default of std::allocator<T> where T must be type of
element held by container
■ std::allocator employs operator new and operator delete for memory
allocation
■ in many contexts, default allocator is quite adequate
memory
■ pointer:
2 pointer type used to refer to storage obtained from allocator (not necessarily
T*)
2 optional: default of T* provided by allocator_traits
■ const_pointer:
2 const version of pointer
2 optional: default of const T* provided by allocator_traits
■ pointer allocate(size_type n):
2 allocate storage suitable for n objects of type T
■ void deallocate(pointer ptr, size_type n):
2 deallocates storage pointed to by ptr, where ptr must have been obtained
by previous call to allocate and n must match value given in that call
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1344
Allocator Members (Continued)
1 #include "mallocator.hpp"
2 #include <cassert>
3 #include <vector>
4 #include <type_traits>
5
6 int main() {
7 std::vector<int, mallocator<int>> v;
8 // uses mallocator<int> for memory allocation
9 std::vector<int> w;
10 // or equivalently, std::vector<int, std::allocator<int>>
11 // uses std::allocator<int> for memory allocation
12 static_assert(!std::is_same_v<decltype(v)::allocator_type,
13 decltype(w)::allocator_type>);
14 for (int i = 0; i < 128; ++i) {
15 v.push_back(42);
16 w.push_back(42);
17 }
18 std::vector<int, mallocator<int>> x;
19 assert(v.get_allocator() == x.get_allocator());
20 }
1 #include <cstddef>
2 #include <limits>
3 #include <memory>
4 #include <new>
5
6 template <std::size_t N, std::size_t Align = alignof(std::max_align_t)>
7 class arena {
8 public:
9 arena() : ptr_(buf_) {}
10 arena(const arena&) = delete;
11 arena& operator=(const arena&) = delete;
12 ~arena() = default;
13 constexpr std::size_t alignment() const {return Align;}
14 constexpr std::size_t capacity() const {return N;}
15 constexpr std::size_t used() const {return ptr_ - buf_;}
16 constexpr std::size_t free() const {return N - used();}
17 template <std::size_t ReqAlign> void* allocate(std::size_t n);
18 void deallocate(void* ptr, std::size_t n) {}
19 void clear() {ptr_ = buf_;}
20 private:
21 template <std::size_t ReqAlign>
22 static char* align(char* ptr, std::size_t n, std::size_t max);
23 alignas(Align) char buf_[N]; // storage buffer
24 char* ptr_; // pointer to first unused byte
25 };
70 private:
71 template <class T1, std::size_t N1, std::size_t A1, class T2,
72 std::size_t N2, std::size_t A2>
73 friend bool operator==(const salloc<T1, N1, A1>&,
74 const salloc<T2, N2, A2>&);
75 template <class, std::size_t, std::size_t> friend class salloc;
76 arena_type* a_; // arena from which to allocate storage
77 };
78
79 template <class T1, std::size_t N1, std::size_t A1, class T2, std::size_t N2,
80 std::size_t A2>
81 inline bool operator==(const salloc<T1, N1, A1>& a,
82 const salloc<T2, N2, A2>& b)
83 {return N1 == N2 && A1 == A2 && a.a_ == b.a_;}
84
85 template <class T1, std::size_t N1, std::size_t A1, class T2, std::size_t N2,
86 std::size_t A2>
87 inline bool operator!=(const salloc<T1, N1, A1>& a,
88 const salloc<T2, N2, A2>& b)
89 {return !(a == b);}
1 #include <vector>
2 #include <list>
3 #include <iostream>
4 #include "salloc.hpp"
5
6 int main() {
7 using alloc = salloc<int, 1024, sizeof(int)>;
8 alloc::arena_type a;
9 std::vector<int, alloc> v{{0, 1, 2, 3}, a};
10 std::vector<int, alloc> w{{0, 2, 4, 6}, a};
11 std::list<int, alloc> p{{1, 3, 5, 7}, a};
12 std::cout << a.free() << ’\n’;
13 v.push_back(42);
14 for (auto&& i : v) {std::cout << i << ’\n’;}
15 for (auto&& i : w) {std::cout << i << ’\n’;}
16 for (auto&& i : p) {std::cout << i << ’\n’;}
17 std::cout << a.free() << ’\n’;
18
19 // std::vector<int, alloc> x(1024);
20 // std::list<int, alloc> y;
21 // ERROR: allocator cannot be default constructed
22 }
1 #include <memory>
2 #include <type_traits>
3 #include <boost/interprocess/managed_shared_memory.hpp>
4 #include <boost/interprocess/allocators/allocator.hpp>
5 #include <iostream>
6
7 template <class T> void print(std::ostream& out = std::cout) {
8 out << std::is_same_v<typename T::pointer, typename T::value_type*> << ’ ’
9 << std::is_same_v<typename T::const_pointer,
10 const typename T::value_type*> << ’ ’
11 << T::is_always_equal::value << ’ ’
12 << T::propagate_on_container_move_assignment::value << ’ ’
13 << T::propagate_on_container_copy_assignment::value << ’ ’
14 << T::propagate_on_container_swap::value << ’\n’;
15 }
16
17 int main() {
18 namespace bi = boost::interprocess;
19 print<std::allocator_traits<std::allocator<int>>>();
20 print<std::allocator_traits<bi::allocator<int,
21 bi::managed_shared_memory::segment_manager>>>();
22 }
23
24 /* Output:
25 1 1 1 1 0 0
26 0 0 0 0 0 0
27 */
■ consider container class template called optval that can hold optional
value
■ class templated on element type T and allocator type
■ container object in one of two states:
1 holding value of type T
1 #include <vector>
2 #include <scoped_allocator>
3 #include <boost/interprocess/managed_shared_memory.hpp>
4 #include <boost/interprocess/allocators/adaptive_pool.hpp>
5
6 namespace bi = boost::interprocess;
7
8 template <class T>
9 using alloc = typename bi::adaptive_pool<T, typename
10 bi::managed_shared_memory::segment_manager>;
11
12 int main () {
13 using row = std::vector<int, alloc<int>>;
14 using matrix = std::vector<row,
15 std::scoped_allocator_adaptor<alloc<row>>>;
16 bi::managed_shared_memory s(bi::create_only, "data", 8192);
17 matrix v(s.get_segment_manager());
18 v.resize(4);
19 for (int i = 0; i < 4; ++i) {v[i].push_back(0);}
20 bi::shared_memory_object::remove("data");
21 }
References
Concurrency
Preliminaries
Multithreaded Programming
■ Keep all of the processor cores busy (i.e., fully utilize all cores).
2 Most modern systems have multiple processor cores, due to having either
multiple processors or a single processor that is multicore.
2 A single thread cannot fully utilize the computational resources available in
such systems.
■ Keep processes responsive.
2 In graphics applications, keep the GUI responsive while the application is
performing slow operations such as I/O.
2 In network server applications, keep the server responsive to new
connections while handling already established ones.
■ Simplify the coding of cooperating tasks.
2 Some programs consist of several logically distinct tasks.
2 Instead of having the program manage when the computation associated
with different tasks is performed, each task can be placed in a separate
thread and the operating system can perform scheduling.
2 For certain types of applications, multithreading can significantly reduce the
conceptual complexity of the program.
2 x = 1; a = y; y = 1; b = x;
2 y = 1; b = x; x = 1; a = y;
■ Suppose that the compiler makes the same optimization to the code for
thread 1 as on the previous slide, yielding the code below.
Optimized Thread 1 Code (Unchanged) Thread 2 Code
y = 1; if (y == 1) {
x = 1; assert(x == 1);
// ... }
■ Thread 2 can observe x and y being modified in the wrong order (i.e., an
order that is inconsistent with SC execution).
■ The assertion in thread 2 can never fail in the original program, but can
sometimes fail in the optimized program.
■ In a multithreaded program, the reordering of loads and stores must be
avoided if SC is to be maintained.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1392
Store-Buffer Example: Without Store Buffer
■ Consider the program below, where x, y, a, and b are integer variables, all
initially zero.
Thread 1 Code Thread 2 Code
x = 1; y = 1;
a = y; b = x;
■ Some possible sequentially-consistent executions of the program include:
2 x = 1; y = 1; b = x; a = y; (a is 1, b is 1)
2 y = 1; x = 1; a = y; b = x; (a is 1, b is 1)
2 x = 1; a = y; y = 1; b = x; (a is 0, b is 1)
2 y = 1; b = x; x = 1; a = y; (a is 1, b is 0)
Processor Memory
Register x
(2)
■ If memory operations are not all atomic, the possibility exists for
something known as a data race.
■ Two memory operations are said to conflict if they access the same
memory location and at least one of the operations is a write.
■ Two conflicting memory operations form a data race if they are from
different threads and can be executed at the same time.
■ A program with data races usually has unpredictable behavior (e.g., due
to torn reads, torn writes, or worse).
■ Example (data race):
2 Consider the multithreaded program listed below, where x, y, and z are
(nonatomic) integer variables shared between threads and are initially zero.
Thread 1 Code Thread 2 Code
x = 1; y = 1;
a = y + z; b = x + z;
2 The program has data races on both x and y.
2 Since z is not modified by any thread, z cannot participate in a data race.
Byte 0 Byte 1
■ Initially, x is 0: 00 00
Byte 0 Byte 1
■ Thread 1 writes 12 to the first byte of x, yielding: 12 00
■ Thread 2 writes 56 and 78 to the first and second bytes of x, respectively,
yielding: Byte
56
0 Byte 1
78
Byte 0 Byte 1
■ Thread 1 writes 34 to the second byte of x, yielding: 56 34
■ The resulting value in x (i.e., 5634) is neither the value written by thread 1
(i.e., 1234) nor the value written by thread 2 (i.e., 5678).
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1399
SC Data-Race Free (SC-DRF) Memory Model
Thread Management
■ all unjoinable thread objects have same ID, distinct from ID of every
joinable thread object ⁓⁓⁓⁓⁓⁓⁓⁓⁓⁓
[C++17 §33.3.2.1/1]
Member Types
Member Name Description
id thread ID type
native_handle_type system-dependent handle type for under-
lying thread entity
Member Functions
Member Name Description
joinable check if thread joinable
get_id get ID of thread
native_handle get native handle for thread
hardware_concurrency (static) get number of concurrent threads
supported by hardware
join wait for thread to finish executing
detach permit thread to execute indepen-
dently
swap swap threads
1 #include <iostream>
2 #include <thread>
3
4 void hello()
5 {
6 std::cout << "Hello World!\n";
7 }
8
9 int main()
10 {
11 std::thread t(hello);
12 t.join();
13 }
1 #include <iostream>
2 #include <thread>
3
4 int main()
5 {
6 std::thread t([](){
7 std::cout << "Hello World!\n";
8 });
9 t.join();
10 }
1 #include <iostream>
2 #include <thread>
3
4 void hello()
5 {
6 std::cout << "Hello World!\n";
7 }
8
9 int main()
10 {
11 std::jthread t(hello);
12 // t.join() not required
13 }
1 #include <iostream>
2 #include <thread>
3
4 int main()
5 {
6 std::jthread t([](){
7 std::cout << "Hello World!\n";
8 });
9 // t.join() not required
10 }
1 #include <iostream>
2 #include <vector>
3 #include <utility>
4 #include <thread>
5
6 void doWork(const std::vector<int>& v) {
7 for (auto i : v) {
8 std::cout << i << ’\n’;
9 }
10 }
11
12 int main() {
13 std::vector v{1, 2, 3, 4};
14
15 // copy semantics
16 std::jthread t1(doWork, v);
17
18 // move semantics
19 std::jthread t2(doWork, std::move(v));
20 }
1 #include <iostream>
2 #include <vector>
3 #include <functional>
4 #include <thread>
5
6 void doWork(const std::vector<int>& v) {
7 for (auto i : v) {
8 std::cout << i << ’\n’;
9 }
10 }
11
12 int main() {
13 std::vector v{1, 2, 3, 4};
14
15 // copy semantics
16 std::jthread t1(doWork, v);
17
18 // reference semantics
19 std::jthread t2(doWork, std::ref(v));
20 }
1 #include <thread>
2 #include <iostream>
3 #include <utility>
4
5 // Return a thread that prints a greeting message.
6 std::jthread makeThread() {
7 return std::jthread([](){
8 std::cout << "Hello World!\n";
9 });
10 }
11
12 // Return the same thread that was passed as an argument.
13 std::jthread identity(std::jthread t) {
14 return t;
15 }
16
17 int main() {
18 std::jthread t1(makeThread());
19 std::jthread t2(std::move(t1));
20 t1 = std::move(t2);
21 t1 = identity(std::move(t1));
22 }
1 #include <iostream>
2 #include <vector>
3 #include <algorithm>
4 #include <chrono>
5 #include <thread>
6 #include <numeric>
7
8 void threadFunc(const std::vector<int>* v) {
9 std::cout << std::accumulate(v->begin(), v->end(), 0)
10 << ’\n’;
11 }
12
13 void startThread() {
14 std::vector<int> v(1000000, 1);
15 std::jthread t(threadFunc, &v);
16 t.detach();
17 // v is destroyed here but detached thread
18 // may still be using v
19 }
20
21 int main() {
22 startThread();
23 // Give the thread started by startThread
24 // sufficient time to complete its work.
25 std::this_thread::sleep_for(std::chrono::seconds(5));
26 }
Name Description
get_id get ID of current thread
yield suggest rescheduling current thread so as to allow
other threads to run
sleep_for blocks execution of current thread for at least
specified duration
sleep_until blocks execution of current thread until specified
time reached
1 #include <thread>
2 #include <iostream>
3
4 // main thread ID
5 std::jthread::id mainThread;
6
7 void func() {
8 if (std::this_thread::get_id() == mainThread) {
9 std::cout << "called by main thread\n";
10 } else {
11 std::cout << "called by secondary thread\n";
12 }
13 }
14
15 int main() {
16 mainThread = std::this_thread::get_id();
17 std::jthread t([](){
18 // call func from secondary thread
19 func();
20 });
21 // call func from main thread
22 func();
23 }
1 #include <chrono>
2 #include <format>
3 #include <iostream>
4 #include <stop_token>
5 #include <thread>
6
7 void worker(std::stop_token token) {
8 unsigned long long count = 0;
9 while (!token.stop_requested()) {++count;}
10 std::cout << std::format("count: {}\n", count);
11 }
12
13 int main() {
14 using namespace std::chrono_literals;
15 std::jthread t1(worker);
16 std::jthread t2(worker);
17 std::this_thread::sleep_for(100ms);
18 t2.request_stop();
19 std::this_thread::sleep_for(400ms);
20 t1.request_stop();
21 }
1 #include <iostream>
2 #include <vector>
3 #include <thread>
4
5 thread_local int counter = 0;
6
7 void doWork(int id) {
8 static const char letters[] = "abcd";
9 for (int i = 0; i < 10; ++i) {
10 std::cout << letters[id] << counter << ’\n’;
11 ++counter;
12 }
13 }
14
15 int main() {
16 std::vector<std::thread> workers;
17 for (int i = 1; i <= 3; ++i) {
18 // invoke doWork in new thread
19 workers.emplace_back(doWork, i);
20 }
21 // invoke doWork in main thread
22 doWork(0);
23 for (auto& t : workers) {t.join();}
24 }
■ above code has data race on balance object (i.e., more than one thread
may access balance at same time with at least one thread writing)
1 #include <iostream>
2 #include <thread>
3
4 unsigned long long counter = 0;
5
6 void func() {
7 for (unsigned long long i = 0; i < 1’000’000; ++i) {
8 ++counter;
9 }
10 }
11
12 int main() {
13 std::jthread t1(func);
14 std::jthread t2(func);
15 t1.join();
16 t2.join();
17 std::cout << counter << ’\n’;
18 }
1 #include <iostream>
2 #include <thread>
3 #include <unordered_set>
4
5 class IntSet {
6 public:
7 bool contains(int i) const
8 {return s_.find(i) != s_.end();}
9 void add(int i)
10 {s_.insert(i);}
11 private:
12 std::unordered_set<int> s_;
13 };
14
15 IntSet s;
16
17 int main() {
18 std::jthread t1([](){
19 for (int i = 0; i < 10’000; ++i) {s.add(2 * i);}
20 });
21 std::jthread t2([](){
22 for (int i = 0; i < 10’000; ++i) {s.add(2 * i + 1);}
23 });
24 t1.join(); t2.join();
25 std::cout << s.contains(1000) << ’\n’;
26 }
Mutexes
1 #include <iostream>
2 #include <thread>
3 #include <mutex>
4
5 std::mutex m;
6 unsigned long long counter = 0;
7
8 void func() {
9 for (unsigned long long i = 0; i < 1’000’000; ++i) {
10 m.lock(); // acquire mutex
11 ++counter;
12 m.unlock(); // release mutex
13 }
14 }
15
16 int main() {
17 std::jthread t1(func);
18 std::jthread t2(func);
19 t1.join();
20 t2.join();
21 std::cout << counter << ’\n’;
22 }
■ earlier example
above code fixes data race from ..................
Member Types
Name Description
mutex_type underlying mutex type if only one
1 #include <iostream>
2 #include <mutex>
3 #include <thread>
4 #include <unordered_set>
5
6 class IntSet {
7 public:
8 bool contains(int i) const {
9 std::scoped_lock lock(m_);
10 return s_.find(i) != s_.end();
11 }
12 void add(int i) {
13 std::scoped_lock lock(m_);
14 s_.insert(i);
15 }
16 private:
17 std::unordered_set<int> s_;
18 mutable std::mutex m_;
19 };
20
21 IntSet s;
22
23 int main() {
24 std::jthread t1([](){
25 for (int i = 0; i < 10’000; ++i) {s.add(2 * i);}
26 });
27 std::jthread t2([](){
28 for (int i = 0; i < 10’000; ++i) {s.add(2 * i + 1);}
29 });
30 t1.join(); t2.join();
31 std::cout << s.contains(1000) << ’\n’;
32 }
Locking Functions
Name Description
lock acquire mutex, blocking if not available
try_lock try to lock mutex without blocking
try_lock_for try to lock mutex without blocking
try_lock_until try to lock mutex without blocking
unlock release mutex
Observer Functions
Name Description
owns_lock tests if lock owns associated mutex
operator bool tests if lock owns associated mutex
1 #include <vector>
2 #include <iostream>
3 #include <thread>
4 #include <mutex>
5 #include <chrono>
6
7 std::timed_mutex m;
8
9 void doWork() {
10 for (int i = 0; i < 10000; ++i) {
11 std::unique_lock lock(m, std::defer_lock);
12 int count = 0;
13 while (!lock.try_lock_for(
14 std::chrono::microseconds(1))) {++count;}
15 std::cout << count << ’\n’;
16 }
17 }
18
19 int main() {
20 std::vector<std::jthread> workers;
21 for (int i = 0; i < 16; ++i) {
22 workers.emplace_back(doWork);
23 }
24 }
■ A recursive mutex is a mutex for which a thread may own multiple locks
at the same time.
■ After a mutex is first locked by thread A, thread A can acquire additional
locks on the mutex (without releasing the lock already held).
■ The mutex is not available to other threads until thread A releases all of its
locks on the mutex.
■ A recursive mutex is typically used when code that locks a mutex must call
other code that locks the same mutex (in order to avoid deadlock).
■ For example, a function that acquires a mutex and recursively calls itself
(resulting in the mutex being relocked) would need to employ a recursive
mutex.
■ A recursive mutex has more overhead than a nonrecursive mutex.
■ Code that uses recursive mutexes can often be more difficult to
understand and therefore more prone to bugs.
■ Consequently, the use of recursive mutexes should be avoided if possible.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1453
Recursive Mutex Classes
Other Functions
Name Description
native_handle get handle for underlying mutex entity
1 #include <iostream>
2 #include <vector>
3 #include <thread>
4 #include <mutex>
5
6 std::once_flag flag;
7
8 void worker(int id) {
9 std::call_once(flag, [id](){
10 // This code will be invoked only once.
11 std::cout << "first: " << id << ’\n’;
12 });
13 }
14
15 int main() {
16 std::vector<std::jthread> threads;
17 for (int i = 0; i < 16; ++i) {
18 threads.emplace_back(worker, i);
19 }
20 }
Condition Variables
Member Types
Name Description
native_handle_type system-dependent handle type for underlying con-
dition variable entity
1 #include <functional>
2 #include <thread>
3 #include <vector>
4 #include "latch_1.hpp"
5
6 void worker(latch& ready) {
7 // ... (perform very slow initialization)
8 // wait for all threads to complete initialization
9 ready.count_down_and_wait();
10 // ... (perform real work)
11 }
12
13 int main() {
14 constexpr int num_workers = 32;
15 latch ready(num_workers);
16 std::vector<std::jthread> workers;
17 for (int i = 0; i < num_workers; ++i) {
18 workers.emplace_back(worker, std::ref(ready));
19 }
20 for (auto& i : workers) {i.join();}
21 }
Promise Future
Shared
State
Other Functions
Name Description
swap swap two promise objects
get_future get future associated with promised
result
set_value set result to specified value
set_value_at_thread_exit set result to specified value while de-
livering notification only at thread exit
set_exception set result to specified exception
set_exception_at_thread_exit set result to specified exception while
delivering notification only at thread
exit
Other Functions
Name Description
share transfer shared state to shared_future object
get get result
valid check if future object refers to shared state
wait wait for result to become available
wait_for wait for result to become available or time duration to expire
wait_until wait for result to become available or time point to be
reached
1 #include <future>
2 #include <thread>
3 #include <iostream>
4 #include <utility>
5
6 double computeValue() {
7 return 42.0;
8 }
9
10 void produce(std::promise<double> p) {
11 // write result to promise
12 p.set_value(computeValue());
13 }
14
15 int main() {
16 std::promise<double> p;
17 auto f = p.get_future(); // save future before move
18 std::jthread producer(produce, std::move(p));
19 std::cout << f.get() << ’\n’;
20 producer.join();
21 }
1 #include <iostream>
2 #include <vector>
3 #include <thread>
4 #include <future>
5
6 void consume(std::shared_future<int> f) {
7 std::cout << f.get() << ’\n’;
8 }
9
10 int main() {
11 std::promise<int> p;
12 std::shared_future f = p.get_future().share();
13 std::vector<std::jthread> consumers;
14 for (int i = 0; i < 16; ++i) {
15 consumers.emplace_back(consume, f);
16 }
17 p.set_value(42);
18 for (auto& i : consumers) {
19 i.join();
20 }
21 }
1 #include <future>
2 #include <iostream>
3
4 double computeValue() {
5 return 42.0;
6 }
7
8 int main() {
9 // invoke computeValue function asynchronously in
10 // separate thread
11 auto f = std::async(std::launch::async, computeValue);
12 std::cout << f.get() << ’\n’;
13 }
1 #include <iostream>
2 #include <vector>
3 #include <cmath>
4 #include <future>
5 #include <stdexcept>
6
7 double squareRoot(double x) {
8 if (x < 0.0) {
9 throw std::domain_error(
10 "square root of negative number");
11 }
12 return std::sqrt(x);
13 }
14
15 int main() {
16 std::vector values{1.0, 2.0, -1.0};
17 std::vector<std::future<double>> results;
18 for (auto x : values) {
19 results.push_back(std::async(squareRoot, x));
20 }
21 for (auto& x : results) {
22 try {
23 std::cout << x.get() << ’\n’;
24 } catch (const std::domain_error&) {
25 std::cout << "error\n";
26 }
27 }
28 }
Other Functions
Name Description
valid check if task object currently associated
with shared state
swap swap two task objects
get_future get future associated with promised result
operator() invoke function
make_ready_at_thread_exit invoke function ensuring result ready only
once current thread exits
reset reset shared state, abandoning any previ-
ously stored result
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1496
Example: Packaged Task
1 #include <iostream>
2 #include <thread>
3 #include <future>
4 #include <utility>
5 #include <chrono>
6
7 int getMeaningOfLife() {
8 // Let the suspense build before providing the answer.
9 std::this_thread::sleep_for(std::chrono::milliseconds(
10 1000));
11 // Return the answer.
12 return 42;
13 }
14
15 int main() {
16 std::packaged_task<int()> pt(getMeaningOfLife);
17 // Save the future.
18 auto f = pt.get_future();
19 // Start a thread running the task and detach the thread.
20 std::jthread t(std::move(pt));
21 t.detach();
22 // Get the result via the future.
23 int result = f.get();
24 std::cout << "The meaning of life is " << result << ’\n’;
25 }
1 #include <iostream>
2 #include <cmath>
3 #include <thread>
4 #include <future>
5
6 double power(double x, double y) {
7 return std::pow(x, y);
8 }
9
10 int main() {
11 // invoke task in main thread
12 std::packaged_task<double(double, double)> task(power);
13 task(0.5, 2.0);
14 std::cout << task.get_future().get() << ’\n’;
15 // reset shared state
16 task.reset();
17 // invoke task in new thread
18 auto f = task.get_future();
19 std::jthread t(std::move(task), 2.0, 0.5);
20 t.detach();
21 std::cout << f.get() << ’\n’;
22 }
Atomics
■ These types provide a uniform interface for accessing the atomic memory
operations of the underlying hardware.
Member Functions
Member Name Description
constructor constructs object
clear atomically sets flag to false
test_and_set atomically sets flag to true and obtains its pre-
vious value
Basic
Member Name Description
constructor constructs object
operator= atomically store value into atomic object
is_lock_free check if atomic object is lock free
store atomically replaces value of atomic object
with given value
load atomically reads value of atomic object
operator T obtain result of load
exchange atomically replaces value of atomic object
with given value and obtain value of previous
value
compare_exchange_weak similar to exchange_strong but may fail spu-
riously
compare_exchange_strong atomically compare value of atomic object to
given value and perform exchange if equal or
load otherwise
Fetch
Member Name Description
fetch_add atomically adds given value to value stored in atomic object
and obtains value held previously
fetch_sub atomically subtracts given value from value stored in atomic
object and obtains value held previously
fetch_and atomically replaces value of atomic object with bitwise AND
of atomic object’s value and given value, and obtains value
held previously
fetch_or atomically replaces value of atomic object with bitwise OR
of atomic object’s value and given value, and obtains value
held previously
fetch_xor atomically replaces value of atomic object with bitwise XOR
of atomic object’s value and given value, and obtains value
held previously
Compound Assignment
Member Name Description
operator+= atomically adds given value to value stored in atomic
object
operator-= atomically subtracts given value from value stored in
atomic object
operator&= atomically performs bitwise AND of given value with
value stored in atomic object
operator|= atomically performs bitwise OR of given value with
value stored in atomic object
operator^= atomically performs bitwise XOR of given value with
value stored in atomic object
Constants
Member Name Description
is_always_lock_free indicates if type always lock free
1 #include <atomic>
2
3 template <class T>
4 void atomicIncrement(std::atomic<T>& x) {
5 T curValue = x;
6 while (!x.compare_exchange_weak(curValue,
7 curValue + 1)) {}
8 }
■ The value read for x in operation B will not necessarily be 1, since the
result of A may not yet be visible to thread 2 (e.g., due to caching).
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1518
Sequenced-Before Relationships
■ Given two operations A and B performed in the same thread, the
operation A is sequenced before B if A precedes B in program order (i.e.,
source-code order).
■ Sequenced-before relationships are transitive (i.e., if A is sequenced
before B, and B is sequenced before C, then A is sequenced before C).
■ Example: In the code below, statement A is sequenced before
statement B; B is sequenced before statement C; and, by transitivity, A is
sequenced before C.
x = 1; // A
y = 2; // B
z = x + 1; // C
■ Example:
2 Consider the line of code below, which performs (in order) the following
operations: 1) multiplication, 2) addition, and 3) assignment.
y = a * x + b; // (y = ((a * x) + b);
2 Multiplication is sequenced before addition.
2 Addition is sequenced before assignment.
2 Thus, by transitivity, multiplication is sequenced before assignment.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1519
Sequenced-Before Relationships (Continued)
happens before) B; or
2 A and B are in different threads and A inter-thread happens before B.
2 if a call to foo is made prior to a call to bar returning, then the call to foo
m.lock();
A x = 1; m.lock();
B y = x;
m.unlock();
■ since unlock synchronizes with lock, A happens before B; thus, for timing
shown, B must see 1 for x
2 acquire-release (std::memory_order_acq_rel)
2 acquire (std::memory_order_acquire)
2 release (std::memory_order_release)
2 consume (std::memory_order_consume)
2 relaxed (std::memory_order_relaxed)
simultaneously (i.e., total order for all writes to all atomic objects); and
2 whether operations on atomic objects in different threads can establish a
synchronization relationship (namely, a synchronizes-with or
dependency-ordered-before [discussed later] relationship).
■ The models listed from strongest (i.e., makes the most guarantees) to
weakest (i.e., makes the least guarantees) are:
1 sequentially consistent,
2 acquire release,
3 consume release, and
4 relaxed.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1528
Memory Models (Continued 1)
■ These models are hierarchical in the sense that each model makes at
least all of the same guarantees as its weaker counterparts.
■ As we proceed from stronger to weaker models, more guarantees are lost.
■ A stronger model may require additional synchronization by hardware,
which can degrade performance.
■ A weaker model may not provide sufficient guarantees for the correct
functioning of code.
■ Using a model that fails to provide sufficient guarantees for correct code
behavior will result in bugs.
■ Also, as the model is weakened, it becomes more difficult to reason about
the behavior of code, leading to incomprehensible code and an increased
likelihood of (often very subtle) bugs.
■ All writes to a particular atomic object M (over its lifetime) occur in some
particular total order, called its modification order.
■ Each atomic object has its own well-defined modification order.
■ For a particular atomic object M , all threads in a program are guaranteed
to see M change in a manner consistent with its modification order.
■ Essentially, this guarantee ensures that, once a given thread has seen a
particular value of an atomic object, a subsequent read by that thread
cannot retrieve an earlier value of the object.
■ If such a guarantee were not made, the memory model would be so weak
as to be impractical to use.
■ Modification order is primarily a conceptual tool that is useful for
describing memory-model behavior.
■ In practice, a thread is unlikely to actually observe every change in the
modification order of an object.
■ For each atomic object M , each thread has its own current position in
object’s modification order.
■ A thread’s current position in the modification order of a particular atomic
object need not be the same for all threads.
■ A read from an atomic object M by a thread T can optionally move T ’s
current position to a later position in the modification order of M and then
returns the value at the current position.
■ A write to an atomic object M by a thread T appends the value to be
written to the modification order of M and updates T ’s current position in
the modification order of M to correspond to the value written.
■ An read-modify-write operation A on an atomic object M reads the last
value in the modification order of M , modifies the value read appropriately,
appends the resulting value to the modification order of M , and updates
T ’s current position in the modification order of M to correspond to the
value written.
■ Although each atomic object has its own well-defined modification order, it
is not necessarily the case that the modification orders for individual
objects can be combined into a single total order over all atomic objects.
■ Practically speaking, the reason for this is the delay in the visibility of
results introduced by store buffers, caches, and so on.
■ If a single total order for writes to all atomic objects is not guaranteed, this
implies that the relative order of changes to different atomic objects need
not appear the same to different threads.
■ Ensuring the existence of a single total order over all atomic objects would
require a significant amount of additional processor synchronization,
which can significantly degrade performance.
■ Therefore, this guarantee is not required to be made in all cases, the idea
being that we only ask for the guarantee when it is needed for correct
code behavior.
■ Observe that thread 1 and thread 2 do not see x and y change in the
same order relative to one another (i.e., thread 1 sees x change before y,
while thread 2 sees y change before x).
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1534
Sequentially-Consistent Model
■ assertion cannot fail: when while loop in r_xy terminates, all threads
must see x as nonzero; when while loop in r_yx terminates, all threads
must see y as nonzero; at least one of these must happen before if
statements in both r_xy and r_yx executed
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1537
Acquire-Release Model
■ For the acquire-release model, the memory order is chosen as follows:
2 a read operation uses the acquire order (std::memory_order_acquire)
2 a write operation uses the release order (std::memory_order_release)
2 a read-modify-write operation uses one of the orders allowed for read and
write operations, or the acquire-release order
(std::memory_order_acq_rel), which results in read acquire and write
release.
■ No total ordering exists on all writes to all atomic objects (unlike in the
sequentially-consistent model).
■ Consequently, threads do not necessarily have to agree on the relative
order in which different atomics objects are modified.
■ A write-release operation W on an atomic object M synchronizes with a
read-acquire operation on M that reads the value written by W (or a value
written by the release sequence headed by W ). [C++17 §32.4/2]
⁓⁓⁓⁓⁓⁓⁓⁓
■ The acquire-release model is useful for situations that involve pairwise
synchronization of threads, such as with mutexes.
■ With the acquire-release model, it is often still possible to reason about
code behavior without too much difficulty.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1538
Example: Acquire-Release Model
■ shared data:
x and y are of type std::atomic<int> and both are initially zero
■ thread 1 code (writes x):
x.store(1, std::memory_order_release);
■ thread 2 code (writes y):
y.store(1, std::memory_order_release);
■ thread 3 code (reads x then y):
int x1 = x.load(std::memory_order_acquire);
int y1 = y.load(std::memory_order_acquire);
■ thread 4 code (reads y then x):
int y2 = y.load(std::memory_order_acquire);
int x2 = x.load(std::memory_order_acquire);
■ no ordering relationship between stores to x and y
■ so, thread 3 and thread 4 do not need to agree about order in which x and
y are modified
■ possible to see x1 == 1 and y1 == 0 in thread 3 (i.e., thread 3 sees x
change before y) and x2 == 0 and y2 == 1 in thread 4 (i.e., thread 4
sees y change before x)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1539
Example: Acquire-Release Model
1 #include <atomic>
2 #include <thread>
3 #include <cassert>
4
5 std::atomic<int> x, y, c;
6
7 void w_x() {x.store(1, std::memory_order_release);}
8
9 void w_y() {y.store(1, std::memory_order_release);}
10
11 void r_xy() {
12 while (!x.load(std::memory_order_acquire)) {}
13 if (y.load(std::memory_order_acquire)) {++c;}
14 }
15
16 void r_yx() {
17 while (!y.load(std::memory_order_acquire)) {}
18 if (x.load(std::memory_order_acquire)) {++c;}
19 }
20
21 int main() {
22 x = 0; y = 0; c = 0;
23 std::jthread t1(w_x), t2(w_y), t3(r_xy), t4(r_yx);
24 t1.join(); t2.join(); t3.join(); t4.join();
25 assert(c != 0); // assertion can fail
26 }
■ assertion can fail: one thread seeing x or y being nonzero does not imply
other thread sees same
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1540
Example: Spinlock Mutex Using std::atomic_flag
1 #include <iostream>
2 #include <thread>
3 #include <atomic>
4
5 class SpinLockMutex {
6 public:
7 SpinLockMutex() {f_.clear();}
8 void lock() {
9 while (f_.test_and_set(std::memory_order_acquire)) {}
10 }
11 void unlock() {f_.clear(std::memory_order_release);}
12 private:
13 std::atomic_flag f_; // true if thread holds mutex
14 };
15
16 SpinLockMutex m;
17 unsigned long long counter = 0;
18
19 void doWork() {
20 for (unsigned long long i = 0; i < 100’000ULL; ++i)
21 {m.lock(); ++counter; m.unlock();}
22 }
23
24 int main() {
25 std::jthread t1(doWork), t2(doWork);
26 t1.join(); t2.join();
27 std::cout << counter << ’\n’;
28 }
■ For the relaxed model, all memory operations use the relaxed order
(std::memory_order_relaxed).
■ Like in the acquire-release model, no total order exists on updates to all
atomic objects (collectively).
■ Operations on the same variable within a single thread satisfy a
happens-before relationship (i.e., within a single thread, accesses to a
single atomic variable must follow program order).
■ Unlike in the acquire-release model, no inter-thread synchronization
relationship is established.
■ No requirement exists on the ordering relative to other threads.
■ The relaxed order is sometime suitable for updating counters (e.g., blind
event counters).
■ Except in very trivial cases, it can be extremely difficult to reason about
the meaning and/or correctness of code that uses relaxed order.
■ assertion can fail: one thread seeing x or y being nonzero does not imply
other thread sees same
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1551
Example: Blind Event Counters
1 #include <vector>
2 #include <iostream>
3 #include <thread>
4 #include <atomic>
5
6 std::atomic<unsigned long long> counter(0);
7
8 void doWork() {
9 for (long i = 0; i < 100’000L; ++i)
10 {counter.fetch_add(1, std::memory_order_relaxed);}
11 }
12
13 int main() {
14 std::vector<std::jthread> workers;
15 for (int i = 0; i < 10; ++i) {workers.emplace_back(doWork);}
16 for (auto& t : workers) {t.join();}
17 std::cout << "counter " << counter << ’\n’;
18 }
References
■ one ABI followed by number of popular C++ compilers is Itanium C++ ABI
■ Itanium C++ ABI builds on various other ABI documents (some of which
are related to either System V or Intel Itanium architecture)
■ amongst other things, Itanium C++ ABI specifies:
2 some implementation details for constructors and destructors
2 name mangling rules
■ web site:
2 https://itanium-cxx-abi.github.io/cxx-abi/abi.html
class objects and non-static data members of T and initializes T object itself
2 complete object constructor: function that creates all virtual base class
object constructor after obtaining storage for new T object from allocation
function (i.e., operator new)
■ if no virtual base classes, complete object constructor and base object
constructor are same (e.g., one function is alias for other)
■ ABI specification seems to suggest that allocating constructor only
required if class has virtual destructor
■ this said, however, GCC appears to never emit code for allocating object
constructor
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1576
Destructors
■ each destructor written by programmer nominally results in compiler
emitting code for multiple functions, which together implement several
variations on destruction
■ destructor for class T nominally associated with three functions:
1 base object destructor: function that performs any clean-up action for T
object itself and destroys non-static data members and non-virtual (direct)
base class objects of T
2 complete object destructor: function that, in addition to actions of base
object destructor, destroys all virtual base class objects of T
3 deleting destructor: function that, in addition to actions of complete object
destructor, invokes appropriate deallocation function for T
■ complete object destructor in charge of destroying virtual base class
objects, whereas base object destructor is not
■ if no virtual base classes, base object destructor and complete object
destructor are same (e.g., one is alias for other)
■ deleting destructor must be emitted when T has virtual destructor;
otherwise, may be emitted but not required
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1577
Name Mangling
■ C++Filt:
2 c++filt program, which is part of GNU Binary Utilities
2 filter that copies character stream from standard input to standard output
replacing any mangled names with their unmangled forms
2 web site: https://www.gnu.org/software/binutils
■ LLVM Cxxfilt:
2 llvm-cxxfilt program, which is part of LLVM software
2 filter that copies character stream from standard input to standard output
replacing any mangled names with their unmangled forms (in similar
fashion as c++filt)
2 LLVM web site: https://llvm.org
■ online name demangler:
2 http://demangler.com
References
1 #include <iostream>
2
3 template <typename Tag>
4 typename Tag::type saved_private_v;
5
6 template <typename Tag, typename Tag::type x>
7 bool save_private_v = (saved_private_v<Tag> = x);
8
9 class Widget {
10 public:
11 Widget(int i) : i_(i) {}
12 private:
13 int i_;
14 int f_() const {return i_;}
15 };
16
17 struct Widget_i_ {using type = int Widget::*;};
18 struct Widget_f_ {using type = int (Widget::*)() const;};
19
20 template bool save_private_v<Widget_i_, &Widget::i_>;
21 template bool save_private_v<Widget_f_, &Widget::f_>;
22
23 int main() {
24 Widget w(42);
25 std::cout << w.*saved_private_v<Widget_i_> << ’\n’;
26 std::cout << (w.*saved_private_v<Widget_f_>)() << ’\n’;
27 }
C++ Compatibility
■ many changes have been made to C++ language and standard library
during evolution of C++ from C++98 to present
■ some changes resulted in incompatibilities between different versions of
C++ standard
■ subsequent slides list some reference material that discusses how C++
standard changed from one version to next
■ knowing such changes helps to understand incompatibilities between
different versions
C Compatibility
1 #include <stdio.h>
2 #include <unistd.h>
3
4 /* Delete a file. */
5 int delete(const char* filename) { /* note function name */
6 return unlink(filename);
7 }
8
9 int main(int argc, char** argv) {
10 if (argc >= 2) {
11 if (delete(argv[1])) {
12 printf("cannot delete file\n");
13 return 1;
14 }
15 }
16 return 0;
17 }
1 #include <stdio.h>
2
3 int plusOne(); /* no arguments specified */
4
5 int main(int argc, char** argv) {
6 printf("%d\n", plusOne(0));
7 return 0;
8 }
9
10 int plusOne(int i) {
11 return i + 1;
12 }
1 #include <stdio.h>
2
3 myfunc() { /* implicit return type */
4 return 3;
5 }
6
7 int main(int argc, char **argv) {
8 int i;
9 i = myfunc();
10 printf("%d\n", i);
11 return 0;
12 }
1 struct outer {
2 struct inner {
3 int i;
4 };
5 int j;
6 };
7
8 struct inner a = {1}; /* inner vs. outer::inner */
9
10 int main(int argc, char** argv) {
11 return 0;
12 }
■ C and C++ both allow nested struct types, but the scoping rules differ.
1 #include <stdio.h>
2
3 int main() {
4 printf("%d\n", sizeof(’a’));
5 }
■ A character literal (such as ’A’) is of type char in C++, but type int
in C.
■ Consequently, the above program will print a value of 1 when compiled
as C++ and a value greater than 1 (namely, the value of sizeof(int))
when compiled as C.
■ Thus, the same source code can have different semantics, depending on
whether it is interpreted as C++ or C.
Libraries
Boost Libraries
Introduction
Iterators
Library Description
Iterator concepts that extend C++ standard iterator requirements
and components for building iterators based on these con-
cepts; includes several iterator adaptors
Input/Output
Library Description
I/O State Savers classes for saving/restoring state associated with
I/O streams
Miscellaneous
Library Description
Program Options process program options via command line or con-
figuration file
Concurrent Programming
Library Description
Fiber userland threads library
Compute parallel/GPU computing library
Lockfree lock-free containers (e.g., stacks and queues)
Process child process management
//www.boost.org/doc/libs/release/doc/html/container.html
1 #include <iostream>
2 #include <cassert>
3 #include <boost/container/flat_set.hpp>
4
5 int main() {
6 namespace bc = boost::container;
7 bc::flat_set<std::string> c;
8 c.reserve(4);
9 c.insert("hi");
10 c.insert("apple");
11 c.insert("bye");
12 c.insert("foo");
13 for (auto&& i : c) {
14 std::cout << i << ’\n’;
15 }
16 std::cout << ’\n’;
17 auto j = c.find("foo");
18 assert(j != c.end() && *j == "foo");
19 c.erase(j);
20 c.shrink_to_fit();
21 for (auto&& i : c) {
22 std::cout << i << ’\n’;
23 }
24 }
//www.boost.org/doc/libs/release/doc/html/intrusive.html
■ hook is class object that must be added to user’s class in order for user’s
class to be usable with intrusive container
■ hook encapsulates data used to manage nodes in container, such as:
2 pointers to successor/predecessor nodes for linked lists
2 pointers to parent/child nodes for trees
■ two kinds of hooks:
1 base hook
2 member hook
■ base hook is included in user’s class as base class object using public
inheritance
■ member hook included in user’s class as public data member
1 #include <iostream>
2 #include <vector>
3 #include <boost/intrusive/list.hpp>
4
5 namespace bi = boost::intrusive;
6
7 struct Widget : public bi::list_base_hook<> {
8 explicit Widget(int i_) : i(i_) {}
9 int i;
10 };
11
12 using WidgetList = bi::list<Widget>;
13
14 int main() {
15 std::vector<Widget> storage;
16 for (int i = 0; i < 10; ++i) {storage.emplace_back(i);}
17 WidgetList widgets;
18 for (auto&& w : storage) {widgets.push_back(w);}
19 for (auto i = widgets.begin(); i != widgets.end(); ++i) {
20 if (i != widgets.begin()) {std::cout << ’ ’;}
21 std::cout << i->i;
22 }
23 std::cout << ’\n’;
24 while (!widgets.empty()) {widgets.erase(widgets.begin());}
25 }
1 #include <iostream>
2 #include <vector>
3 #include <boost/intrusive/list.hpp>
4
5 namespace bi = boost::intrusive;
6
7 struct Alpha {};
8 struct Beta {};
9 struct Widget : public bi::list_base_hook<bi::tag<Alpha>>,
10 public bi::list_base_hook<bi::tag<Beta>> {
11 Widget(int i_) : i(i_) {}
12 int i;
13 };
14
15 int main() {
16 std::vector<Widget> storage;
17 for (int i = 0; i < 10; ++i) {storage.emplace_back(i);}
18 bi::list<Widget, bi::base_hook<bi::list_base_hook<bi::tag<Alpha>>>> a;
19 bi::list<Widget, bi::base_hook<bi::list_base_hook<bi::tag<Beta>>>> b;
20 for (auto&& i : storage) {a.push_back(i); b.push_front(i);}
21 for (auto&& w : a) {std::cout << w.i << ’\n’;}
22 std::cout << ’\n’;
23 for (auto&& w : b) {std::cout << w.i << ’\n’;}
24 while (!a.empty()) {a.erase(a.begin());}
25 while (!b.empty()) {b.erase(b.begin());}
26 }
1 #include <iostream>
2 #include <vector>
3 #include <boost/intrusive/set.hpp>
4
5 namespace bi = boost::intrusive;
6
7 struct Widget : public bi::set_base_hook<> {
8 explicit Widget(int i_) : i(i_) {}
9 bool operator<(const Widget& other) const {return i < other.i;}
10 int i;
11 };
12
13 int main() {
14 int values[] = {1, 3, 5, 7, 9, 0, 2, 4, 6, 8};
15 std::vector<Widget> storage;
16 for (auto i : values) {storage.emplace_back(i);}
17 bi::set<Widget> a;
18 for (auto&& w : storage) {a.insert(a.end(), w);}
19 for (auto&& w : a) {std::cout << w.i << ’\n’;}
20 while (!a.empty()) {a.erase(a.begin());}
21 }
■ for sets and multisets, key type is (by definition) same as value type
■ in case of container such as std::set and std::multiset, find
operations can only perform lookup based on key type of container
■ in case of Boost Intrusive containers, find operation can perform lookup
based on type different from key type
■ accomplished by providing alternate key type and functor class used to
compare alternate key type to value type
■ ordering associated with alternate key type and corresponding
comparison class must be consistent with ordering induced by key type
used by container
■ allowing lookup using type different from key type, eliminates need to
create value-type object in order to perform find operation
■ effectively, this allows set and multiset to provide functionality similar to
map and multimap, respectively
■ since find operations can perform lookups using type different from key
type of container, set and multiset can provide similar functionality to
maps and multimaps
■ can also use key_of_value option for set and multiset class
templates to specify member of value type as key
■ this can be helpful when key corresponds to one of data members in value
type
ndex.html
1 #include <cstddef>
2 #include <iterator>
3 #include <type_traits>
4
5 // singly-linked list node base (for intrusive container)
6 template <class T> struct slist_node_base {
7 slist_node_base(T* next_) : next(next_) {}
8 T* next; // pointer to next node in list
9 };
10
11 // singly-linked list iterator (const and non-const)
12 template <class T> class slist_iter {
13 public:
14 using iterator_category = std::forward_iterator_tag;
15 using value_type = typename std::remove_const_t<T>;
16 using difference_type = std::ptrdiff_t;
17 using reference = T&;
18 using pointer = T*;
19 slist_iter(T* node = nullptr) : node_(node) {}
20 template <class OtherT> requires std::is_convertible_v<OtherT*, T*>
21 slist_iter(const slist_iter<OtherT>& other) : node_(other.node_) {}
22 reference operator*() {return *node_;}
23 pointer operator->() {return node_;}
24 slist_iter& operator++() {
25 node_ = node_->next;
26 return *this;
27 }
28 slist_iter operator++(int) {
29 slist_iter old(*this);
30 node_ = node_->next;
31 return old;
32 }
33 template <class OtherT>
34 bool operator==(const slist_iter<OtherT>& other) const
35 {return node_ == other.node_;}
36 private:
37 template <class> friend class slist_iter;
38 T* node_; // pointer to list node
39 };
1 #include <type_traits>
2 #include <boost/iterator/iterator_facade.hpp>
3
4 template <class T> struct slist_node_base {
5 slist_node_base(T* next_) : next(next_) {}
6 T* next; // pointer to next node in list
7 };
8
9 template <class T> class slist_iter : public boost::iterator_facade<
10 slist_iter<T>, T, boost::forward_traversal_tag> {
11 public:
12 using base = typename boost::iterator_facade<slist_iter<T>, T,
13 boost::forward_traversal_tag>;
14 using typename base::reference;
15 using typename base::value_type;
16 slist_iter(T* node = nullptr) : node_(node) {}
17 template <class OtherT> requires std::is_convertible_v<OtherT*, T*>
18 slist_iter(const slist_iter<OtherT>& other) : node_(other.node_) {}
19 private:
20 reference dereference() const {return *node_;}
21 template <class OtherT> bool equal(const slist_iter<OtherT>& other) const
22 {return node_ == other.node_;}
23 void increment() {node_ = node_->next;}
24 template <class> friend class slist_iter;
25 friend class boost::iterator_core_access;
26 T* node_; // pointer to list node
27 };
1 #include <iostream>
2 #include <vector>
3 #include "iterator_facade_2.hpp"
4
5 struct Node : public slist_node_base<Node> {
6 Node(Node* next_, int value_) : slist_node_base<Node>(next_),
7 value(value_) {}
8 int value;
9 };
10
11 int main() {
12 constexpr int num_nodes = 10;
13 std::vector<Node> nodes; nodes.reserve(num_nodes);
14 for (int i = 0; i < num_nodes - 1; ++i)
15 {nodes.push_back(Node(&nodes[i + 1], i));}
16 nodes.push_back(Node(nullptr, num_nodes - 1));
17 slist_iter<Node> begin(&nodes[0]);
18 slist_iter<Node> end;
19 slist_iter<const Node> cbegin(begin);
20 slist_iter<const Node> cend(end);
21 for (auto i = cbegin; i != cend; ++i) {std::cout << i->value << ’\n’;}
22 slist_iter<Node> i(begin);
23 slist_iter<const Node> ci(cbegin);
24 // slist_iter<Node> j(cbegin); // ERROR
25 i = begin;
26 // i = ci; // ERROR
27 ci = cbegin;
28 ci = i;
29 }
1 #include <compare>
2 #include <iterator>
3 #include <type_traits>
4
5 // array element iterator
6 template <class T> class array_iter {
7 public:
8 using iterator_category = typename std::random_access_iterator_tag;
9 using value_type = std::remove_const_t<T>;
10 using reference = T&;
11 using pointer = T*;
12 using difference_type = std::ptrdiff_t;
13 array_iter(T* ptr = nullptr) : ptr_(ptr) {}
14 template <class OtherT> requires std::is_convertible_v<OtherT*, T*>
15 array_iter(const array_iter<OtherT>& other) : ptr_(other.ptr_) {}
16 reference operator*() const {return *ptr_;}
17 pointer operator->() const {return ptr_;}
18 array_iter& operator++() {
19 ++ptr_;
20 return *this;
21 }
22 array_iter operator++(int) {
23 array_iter old(*this);
24 ++ptr_;
25 return old;
26 }
27 array_iter& operator--() {
28 --ptr_;
29 return *this;
30 }
31 array_iter operator--(int) {
32 array_iter old(*this);
33 --ptr_;
34 return old;
35 }
36 array_iter& operator+=(difference_type n) {
37 ptr_ += n;
38 return *this;
39 }
40 array_iter& operator-=(difference_type n) {
41 ptr_ -= n;
42 return *this;
43 }
44 reference operator[](difference_type n) const {return ptr_[n];}
45 array_iter operator+(difference_type n) const
46 {return array_iter(ptr_ + n);}
47 difference_type operator-(const array_iter& other) const
48 {return ptr_ - other.ptr_;}
49 array_iter operator-(difference_type n) const
50 {return array_iter(ptr_ - n);}
51 template <class OtherT> bool operator==(const array_iter<OtherT>& other)
52 const {return ptr_ == other.ptr_;}
53 template <class OtherT>
54 std::strong_ordering operator<=>(const array_iter<OtherT>& other)
55 const {return ptr_ <=> other.ptr_;}
56 private:
57 template <class> friend class array_iter;
58 T* ptr_; // pointer to array element
59 };
1 #include <boost/iterator/iterator_facade.hpp>
2 #include <type_traits>
3
4 // array element iterator
5 template <class T> class array_iter : public boost::iterator_facade<
6 array_iter<T>, T, boost::random_access_traversal_tag> {
7 public:
8 using typename boost::iterator_facade<array_iter<T>, T,
9 boost::random_access_traversal_tag>::reference;
10 using typename boost::iterator_facade<array_iter<T>, T,
11 boost::random_access_traversal_tag>::difference_type;
12 array_iter(T* ptr = nullptr) : ptr_(ptr) {}
13 template <class OtherT> requires std::is_convertible_v<OtherT*, T*>
14 array_iter(const array_iter<OtherT>& other) : ptr_(other.ptr_) {}
15 private:
16 reference dereference() const {return *ptr_;}
17 template <class OtherT> bool equal(const array_iter<OtherT>& other) const
18 {return ptr_ == other.ptr_;}
19 void increment() {++ptr_;}
20 void decrement() {--ptr_;}
21 void advance(difference_type n) {ptr_ += n;}
22 difference_type distance_to(const array_iter& other) const
23 {return other.ptr_ - ptr_;}
24 template <class> friend class array_iter;
25 friend class boost::iterator_core_access;
26 T* ptr_; // pointer to array element
27 };
1 #include <iostream>
2 #include <cassert>
3 #include "iterator_facade_1.hpp"
4
5 int main() {
6 char buffer[] = "Hello, World!\n";
7 std::size_t length = sizeof(buffer) - 1;
8 array_iter<char> begin(buffer);
9 array_iter<char> end(buffer + length);
10 array_iter<const char> cbegin = begin;
11 array_iter<const char> cend = end;
12 assert(begin + length == end);
13 assert(cbegin + length == end);
14 for (auto i = cbegin; i != cend; ++i)
15 {std::cout << *i << ’\n’;}
16 array_iter<char> i(begin);
17 array_iter<const char> ci(cbegin);
18 // array_iter<char> j(cbegin); // ERROR
19 i = begin;
20 // i = ci; // ERROR
21 ci = cbegin;
22 ci = i;
23 }
//www.boost.org/doc/libs/release/doc/html/process.html
1 #include <boost/process.hpp>
2 #include <iostream>
3
4 namespace bp = boost::process;
5
6 int main() {
7 auto env = boost::this_process::environment();
8 bp::environment child_env = env;
9 child_env["ANSWER"] = "42";
10 child_env["MY_PATH"] = {"/bin", "/usr/bin"};
11 bp::child child(bp::search_path("printenv"), child_env);
12 child.wait();
13 if (child.exit_code())
14 {std::cerr << "child failed\n"; return 1;}
15 }
Miscellaneous Examples
1 #include <iostream>
2 #include <cassert>
3 #include <boost/rational.hpp>
4 #include <exception>
5
6 int main() {
7 using boost::rational;
8 const rational<int> zero;
9 rational<int> three(3);
10 rational<int> ninth(1, 9);
11 rational<int> third(1, 3);
12 auto result = three * ninth;
13 assert(result == third);
14 try {
15 std::cout << three / zero << ’\n’;
16 } catch (const boost::bad_rational& e) {
17 std::cout << "bad rational " << e.what() << ’\n’;
18 }
19 // rational<int> x(1.5); // ERROR: no matching call
20 // result = 3.0; // ERROR: no matching call
21 result = 42;
22 assert(result == rational<int>(42));
23 std::cout << result << ’\n’;
24 }
References
f a
c d
a d g b e h c f
Linear Sequence h g
e b
Circular Sequence
■ linear sequence:
2 has well defined first and last element
2 fits well with iterator model
■ circular sequence:
2 does not have well defined first and last element
2 does not fit well with iterator model
■ iterators are very useful, but intended for use with linear sequences of
elements (i.e., sequences with well-defined first and last element)
■ often want iterator-like functionality for circular sequences of elements
■ circulator: object that allows iteration over elements in circular sequence
of elements
■ examples of circulator types:
2 type to allow iteration over all halfedges incident on vertex in polygon mesh
2 type to allow iteration over all halfedges incident on facet in polygon mesh
■ circulators come in const and mutable (i.e., non-const) forms
■ mutable circulator can be used to modify referenced element, while const
circulator cannot
Geometry Kernels
1 #include <iostream>
2 #include <CGAL/MP_Float.h>
3
4 int main() {
5 CGAL::MP_Float x;
6 CGAL::MP_Float y;
7 if (!(std::cin >> x >> y)) {return 1;}
8 if (x < y) {
9 std::cout << x << " is less than " << y << ’\n’;
10 }
11 CGAL::MP_Float z = -(x + y) * (x - y) + x;
12 std::cout << z << ’\n’;
13 }
■ represent geometric objects (e.g., point, line, line segment, ray, plane,
triangle, circle, )
■ points in 2 or 3 dimensions
■ provide operations on geometric objects (e.g., intersection, composition)
■ allow certain conditions to be tested involving geometric objects (e.g.,
collinear, coplanar, equality)
■ coordinate representation
■ exact or inexact constructions
■ exact or inexact predicates
■ in practice, almost always require exact predicates
■ if code well designed, need for exact constructions can usually be avoided
■ for T chosen as any numeric type that has roundoff/overflow error (e.g.,
float, double, long double), the following kernels do not provide
exact constructions or exact predicates:
Simple_cartesian<T>
Cartesian<T>
Simple_homogeneous<T>
Homogeneous<T>
■ class to convert kernel with inexact predicates into one with exact
predicates
■ declared as:
template <class K> Filtered_kernel<K>
■ K is kernel from which to make filtered kernel
■ predicates of K replaced by predicates using numeric type Interval_nt
■ if interval arithmetic can yield reliable answer, result used
■ otherwise, exception thrown and caught by class and predicate using
MP_Float used
■ for exact predicates with Simple_cartesian<double>, use:
Filtered_kernel<Simple_cartesian<double>> or equivalently
Exact_predicates_inexact_constructions_kernel
■ Exact_predicates_inexact_constructions_kernel very commonly
used
■ exact predicate cannot at any point rely on a computation that is not exact
■ no floating point arithmetic (since it has roundoff error)
■ no integer arithmetic that might overflow
■ no inexact constructions
■ no inexact predicates
■ Filtered_predicate may be helpful
Polygon Meshes
Basic Types
Type Description
Vertex vertex type
Halfedge halfedge type
Facet facet type
Point_3 point type (for vertices)
Handles
Type Description
Vertex_const_handle const handle to vertex
Vertex_handle handle to vertex
Halfedge_const_handle const handle to halfedge
Halfedge_handle handle to halfedge
Facet_const_handle const handle to facet
Facet_handle handle to facet
Iterators
Type Description
Vertex_const_iterator const iterator over all vertices
Vertex_iterator iterator over all vertices
Halfedge_const_iterator const iterator over all halfedges
Halfedge_iterator iterator over all halfedges
Facet_const_iterator const iterator over all facets
Facet_iterator iterator over all facets
Edge_const_iterator const iterator over all edges (ev-
ery other halfedge)
Edge_iterator iterator over all edges (every
other halfedge)
Circulators
Type Description
Halfedge_around_vertex_const_circulator const circulator of halfedges
around vertex (CW)
Halfedge_around_vertex_circulator circulator of halfedges
around vertex (CW)
Halfedge_around_facet_const_circulator const circulator of halfedges
around facet (CCW)
Halfedge_around_facet_circulator circulator of halfedges
around facet (CCW)
Iterators
Name Description
vertices_begin iterator for first vertex in mesh
vertices_end past-the-end vertex iterator
halfedges_begin iterator for first halfedge in mesh
halfedges_end past-the-end halfedge iterator
facets_begin iterator for first facet in mesh
facets_end past-the-end facet iterator
edges_begin iterator for first edge in mesh
edges_end past-the-end edge iterator
Combinatorial Predicates
Name Description
is_closed true if no border edges (no boundary)
is_pure_triangle true if all facets are triangles
is_pure_quad true if all facets are quadrilaterals
vertex
Incident Vertex
halfedge
facet
Circulators
Name Description
vertex_begin get halfedge-around-vertex circulator for incident vertex
(CW order)
facet_begin get halfedge-around-facet circulator for incident facet
(CCW order)
h->next_on_vertex()
h->facet()
h->prev() h->next()
h
h->opposite()-> h->vertex()
vertex()
h->opposite()
h->prev_on_vertex()
h->opposite()->
facet()
Example Programs
Initialization
Function Description
glutInit initialize GLUT library
glutInitWindowSize set initial window size for
glutCreateWindow
glutInitWindowPosition set initial window position for
glutCreateWindow
glutInitDisplayMode set initial display mode
State Retrieval
Function Description
glutGet retrieves simple GLUT state (e.g., size or position
of current window)
glutDeviceGet retrieves GLUT device information (e.g., keyboard,
mouse, spaceball, tablet)
glutGetModifiers retrieve modifier key state when certain callbacks
generated (i.e., state of shift, control, and alt keys)
Font Rendering
Function Description
glutBitmapCharacter renders bitmap character using OpenGL
glutBitmapWidth get width of bitmap character
glutStrokeCharacter renders stroke character using OpenGL
glutStrokeWidth get width of stroke character
Other Events
Event Type Description
error error has occurred in GLFW library
Version
Function Description
glfwGetVersion get version of GLFW library
glfwGetVersionString get version string of GLFW library
Window Management
Function Description
glfwIconifyWindow iconifies specified window
glfwRestoreWindow restores (i.e., deiconifies) specified window
glfwMaximizeWindow maximizes specified window
glfwShowWindow make specified window visible
glfwHideWindow hide specified window
glfwFocusWindow bring specified window to front and give it input
focus
glfwSwapBuffers swap front and back buffers of specified win-
dow when rendering with OpenGL or OpenGL
ES
glfwSwapInterval set swap interval for current OpenGL or
OpenGL ES context
Callback Registration
Function Description
glfwSetErrorCallback sets error callback function
glfwSetWindowPosCallback sets window-position callback function
for specified window
glfwSetWindowSizeCallback sets window-size callback function for
specified window
glfwSetWindowCloseCallback sets window-close callback function for
specified window
glfwSetWindowRefreshCallback sets window-refresh callback function
for specified window
glfwSetWindowFocusCallback sets window-focus callback function for
specified window
Event Handling
Function Description
glfwPostEmptyEvent post empty event to event queue
glfwPollEvents process any pending events and return imme-
diately
glfwWaitEvents wait until at least one event is pending, then
process all pending events and return
glfwWaitEventsTimeout wait until at least one event pending or timeout
expires, then process any pending events and
return
Timing
Function Description
glfwGetTime get value of timer in seconds
glfwSetTime set value of timer
glfwGetTimerValue get value of timer in clock ticks
glfwGetTimerFrequency get frequency of clock tick
Clipboard
Function Description
glfwGetClipboardString gets contents of clipboard as string
glfwSetClipboardString sets clipboard to specified string
Monitor Management
Function Description
glfwGetMonitors get currently connected monitors
glfwGetPrimaryMonitor get primary monitor
glfwGetMonitorPos get position of specified monitor’s viewport on
virtual screen
glfwGetMonitorPhysicalSize get physical size of specified monitor
glfwGetMonitorName get name of specified monitor
glfwGetVideoModes get available video modes for specified monitor
glfwGetVideoMode get current video mode of specified monitor
glfwSetGamma set gamma for specified monitor
glfwGetGammaRamp get current gamma ramp for specified monitor
glfwSetGammaRamp set current gamma ramp for specified monitor
Vulkan
Function Description
glfwVulkanSupported tests if Vulkan loader has
been found
glfwGetRequiredInstanceExtensions get Vulkan instance exten-
sions required by GLFW
glfwGetInstanceProcAddress get address of specified
Vulkan instance function
glfwGetPhysicalDevicePresentationSupport test if specified queue fam-
ily can present images
glfwCreateWindowSurface create Vulkan surface for
specified Window
■ matrix types:
2 mat2x2, mat2x3, mat2x4, mat2,
1 #include <iostream>
2 #include <glm/glm.hpp>
3 #include <glm/gtc/matrix_transform.hpp>
4 #include <glm/gtx/string_cast.hpp>
5 #include <cmath>
6
7 int main() {
8 glm::mat4 mv(1.0f);
9 mv = mv * glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f),
10 glm::vec3(1.0f, 0.0f, 0.0f),
11 glm::vec3(0.0f, 0.0f, 1.0f));
12 mv = mv * glm::translate(mv, glm::vec3(1.0f, 1.0f, 1.0f));
13 mv = mv * glm::rotate(mv, glm::radians(90.0f),
14 glm::vec3(0.0f, 0.0f, 1.0f));
15 mv = mv * glm::scale(mv, glm::vec3(1.0f, 1.0f, 2.0f));
16 glm::mat4 p = glm::perspective(glm::radians(90.0f), 1.0f,
17 1.0f, 2.0f);
18 glm::mat4 mvp = p * mv;
19 glm::vec4 v(1.0f, -1.0f, -1.0f, 1.0f);
20 std::cout << glm::to_string(glm::vec3(mv * v)) << ’\n’;
21 std::cout << glm::to_string(glm::vec3(mvp * v)) << ’\n’;
22 std::cout << glm::radians(180.0f) << ’\n’;
23 std::cout << glm::degrees(M_PI) << ’\n’;
24 }
1 #include <GL/glew.h>
2 #include <GL/gl.h>
3 #include <glm/glm.hpp>
4 #include <glm/gtc/type_ptr.hpp>
5
6 void setUniform(GLint loc) {
7 glm::mat4 m(1.0f);
8 // ...
9 glUniform4fv(loc, 4, glm::value_ptr(m));
10 }
CPU GPU
Memory Memory
Type Description
GLboolean boolean
GLbyte 8-bit signed two’s complement integer
GLubyte 8-bit unsigned integer
GLchar 8-bit character
GLshort 16-bit signed two’s complement integer
GLushort 16-bit unsigned integer
GLint 32-bit signed two’s complement integer
GLuint 32-bit unsigned integer
GLfloat single-precision floating-point value
GLdouble double-precision floating-point value
v3 v2 v3 v2 v3 v2 v3 v2
v0 v1 v0 v1 v0 v1 v0 v1
points lines line strip line loop
v3
v4 v3 v2 v5 v3 v1
v4 v2
v0
v5 v0 v1 v4 v2 v0
v5
triangles triangle strip v1 , v6
triangle fan
■ all primitives specified by vertices
Vertex Buffer
Vertex Positions Vertex Normals
Object
Vertex Array
Vertex Attribute Vertex Attribute
Object
Normalized
Device Window
Clipping and Coordinates Viewport Coordinates
Perspective Transformation
Division
Normalized
Device Window
Clipping and Coordinates Viewport Coordinates
Perspective Transformation
Division
v2 v2
v0 v1 v1 v0
Counterclockwise (CCW) Clockwise (CW)
Winding Order Winding Order
Function Description
glClear clear buffer to preset values
glClearColor specify clear values for color buffers
1 #include <cstdlib>
2 #include <string>
3 #include <GL/glew.h>
4 #include <GLFW/glfw3.h>
39 GLuint makeProgram(
40 const std::basic_string<GLchar>& vShaderSource,
41 const std::basic_string<GLchar>& fShaderSource) {
42 GLuint vShader = compileShader(GL_VERTEX_SHADER,
43 vShaderSource);
44 if (!vShader) {return 0;}
45 GLuint fShader = compileShader(GL_FRAGMENT_SHADER,
46 fShaderSource);
47 if (!fShader) {glDeleteShader(vShader); return 0;}
48 GLuint program = glCreateProgram();
49 GLint status = GL_FALSE;
50 if (program) {
51 glAttachShader(program, vShader);
52 glAttachShader(program, fShader);
53 glLinkProgram(program);
54 glGetProgramiv(program, GL_LINK_STATUS, &status);
55 }
56 glDeleteShader(vShader);
57 glDeleteShader(fShader);
58 if (!program) {return 0;}
59 if (status != GL_TRUE)
60 {glDeleteProgram(program); return 0;}
61 return program;
62 }
6 GLuint vao = 0;
Shaders
GPU
samplerCube usamplerCube
sampler1DShadow usampler1DArray
sampler2DShadow usampler2DArray
samplerCubeShadow sampler2DRect
sampler1DArray sampler2DRectShadow
sampler2DArray isampler2DRect
sampler1DArrayShadow usampler2DRect
sampler2DArrayShadow samplerBuffer
isampler1D isamplerBuffer
isampler2D usamplerBuffer
isampler3D sampler2DMS
isamplerCube isampler2DMS
isampler1DArray usampler2DMS
isampler2DArray sampler2DMSArray
usampler1D isampler2DMSArray
usampler2D usampler2DMSArray
usampler3D struct
■ can also form vectors by selecting multiple elements from vector (e.g.,
swizzling and smearing)
■ example:
vec4 v; vec4 u;
vec3 a;
// ...
u = v.wzyx; // vec4(v.w, v.z, v.y, v.x)
u = v.xxyy; // vec4(v.x, v.x, v.y, v.y)
a = v.xyz; // vec3(v.x, v.y, v.z)
u = a.xxxx; // vec4(a.x, a.x, a.x, a.x)
■ selection statements
2 if
2 if-else
2 ternary operator
2 switch
■ looping statements
2 for
2 while
2 do-while
Exponential Functions
Function Description
pow exponentiation function
exp base-e exponentiation function
log natural logarithm function
exp2 base-2 exponentiation function
log2 base-2 logarithm function
sqrt square-root function
inversesqrt reciprocal of square-root function
Common Functions
Function Description
abs absolute-value function
sign signum function
floor floor function
ceil ceiling function
fract fractional-part function
mod modulo function
min minimum of two values
max maximum of two values
clamp clamp value to specified range
mix affine combination of two values
step step function
smoothstep smooth step function
Geometric Functions
Function Description
length length of vector
distance distance between two points
dot dot product
cross cross product
normalize get vector of unit length
faceforward get vector that points in same direction as ref-
erence vector
reflect get vector that points in direction of reflection
refract get vector that points in direction of refraction
Matrix Functions
Function Description
matrixCompMult multiply matrices component-wise
Texture Lookup
Function Description
texture2D perform 2D texture lookup
textureCube perform cubemap texture lookup
■ example:
void calc(float x, int i, out float y, out int j) {
// at this point, y and j are undefined
y = ++x;
j = ++i;
}
void func() {
float a = 0.0;
int b = 0;
float c = 0.0;
int d = 0;
calc(a, b, c, d);
// a and b are unchanged by function call
// c is 1.0, d is 1
}
■ single triangle rendered with vertices having color attributes of red, green,
and blue, with provoking vertex being last vertex
Application Program
Shader Program
Uniform
Variables
Vertex Shader Fragment Shader
Vertex Fragment
Shader Primitive Shader
Assembly
and
Rasterization
Vertex and Fragment Shaders
instanced rendering
■ other inputs associated with vertex attributes from VAO/VBO
■ built-in output variables:
2 vec4 gl_Position: clip-space output position of current vertex
2 float gl_PointSize: pixel width/height of point being rasterized; only
gl_FragCoord.z
■ vec4 output variable for fragment color
Shader Examples
■ vertex shader provided with two attributes per vertex (position and color)
■ want smooth interpolation of color across faces
■ rending output shown below for mesh consisting of single triangle
Fragment Shader
1 #version 330
2
3 in vec4 vColor; // input color (interpolated)
4
5 out vec4 fColor; // output fragment color
6
7 void main() {
8 fColor = vColor;
9 }
■ render triangles to cover entire drawing area and texture map Mandelbrot
set onto triangles using fragment shader
■ some examples of rendering results shown below
v3 = (−1, 1) v2 = (1, 1)
t3 = (− 12 , 12 ) t2 = ( 12 , 12 )
scale/2
center
scale/2
■ application program simply renders two triangles that cover full extent of
viewport ({vk } are positional coordinates; {tk } are texture coordinates)
■ texture coordinate region [− 12 , 12 ] × [− 21 , 12 ] corresponds to full viewport
■ square region in complex plane of width/height scale centered at point
center is mapped onto region [− 12 , 21 ] × [− 12 , 12 ] in texture coordinates
1 #version 330
2
3 in vec3 aPosition; // position vertex attribute
4 in vec3 aTexCoord; // texture-coordinate vertex attribute
5
6 out vec3 vTexCoord; // texture coordinate (interpolated)
7
8 void main() {
9 vTexCoord = aTexCoord;
10 gl_Position = vec4(aPosition, 1.0);
11 }
v1
v′1
c
v′2 v′0
v2 v0
v1
v′1
c
v′2 v′0
v2 v0
■ gap can be filled with triangle strip with vertices: v2 , v′2 , v1 , v′1 , v0 , v′0 , v2 , v′2
1 #version 330
2
3 in vec3 aPosition; // position vertex attribute
4 in vec3 aColor; // color vertex attribute
5
6 out vec3 vColor; // color (interpolated)
7
8 void main() {
9 gl_Position = vec4(aPosition, 1.0);
10 vColor = aColor;
11 }
1 #version 330
2
3 layout(triangles) in; // triangle primitives as input
4 in vec3 vColor[]; // input vertex colors
5
6 layout(triangle_strip, max_vertices=11) out;
7 // triangle strips as output; at most 11 vertices
8 out vec3 gColor; // output color (interpolated)
9
10 uniform mat4 uModelViewProjMatrix;
11 // modelview-projection matrix product
13 void main() {
14 vec3 v[6];
15 for (int i = 0; i < 3; ++i) {v[i] = gl_in[i].gl_Position.xyz;}
16
17 // compute centroid of triangle
18 vec3 c = (v[0] + v[1] + v[2]) / 3.0;
19
20 // compute vertices of shrunk triangle and generate
21 // triangle strip consisting only of shrunk triangle
22 for (int i = 0; i < 3; ++i) {
23 v[i + 3] = c + 0.5 * (v[i] - c);
24 gl_Position = uModelViewProjMatrix * vec4(v[i + 3], 1.0);
25 gColor = vColor[i];
26 EmitVertex();
27 }
28 EndPrimitive();
29
30 // generate triangle strip to fill gap between triangles
31 // introduced by shrinking
32 const int lut[] = int[](2, 5, 1, 4, 0, 3, 2, 5);
33 for (int i = 0; i < 8; ++i) {
34 gl_Position = uModelViewProjMatrix * vec4(v[lut[i]], 1.0);
35 gColor = vec3(0.0, 0.0, 0.0);
36 EmitVertex();
37 }
38 }
1 #version 330
2
3 in vec3 gColor; // input color
4
5 out vec4 fColor; // output color
6
7 void main() {
8 fColor = vec4(gColor, 1.0);
9 }
1 1
2 p0 + 0p1 + 2 p2 0p0 + 12 p1 + 12 p2
≡ ( 21 , 0, 12 ) ≡ (0, 12 , 12 )
1 1 1
3 p0 + 3 p1 + 3 p2
≡ ( 31 , 13 , 13 )
1 1
p0 2 p0 + 2 p1 + 0p2 p1
1p0 + 0p1 + 0p2 ≡ ( 21 , 21 , 0) 0p0 + 1p1 + 0p2
≡ (1, 0, 0) ≡ (0, 1, 0)
1 #version 330
2
3 in vec3 aPosition; // position vertex attribute
4 in vec3 aColor; // color vertex attribute
5
6 out vec3 vColor; // output color (interpolated)
7
8 uniform mat4 uModelViewProjMatrix;
9 // modelview-projection matrix product
10
11 void main() {
12 gl_Position = uModelViewProjMatrix * vec4(aPosition, 1.0);
13 vColor = aColor;
14 }
1 #version 330
2
3 layout(triangles) in; // triangles as input
4 in vec3 vColor[]; // vertex colors
5
6 layout(triangle_strip, max_vertices=3) out;
7 // triangle strips as output; at most 3 vertices
8 out vec3 gColor; // output color
9 noperspective out vec3 gBaryCoord;
10 // output barycentric coordinates (interpolated)
11
12 void main() {
13 const vec3 lut[3] = vec3[3](
14 vec3(1.0, 0.0, 0.0),
15 vec3(0.0, 1.0, 0.0),
16 vec3(0.0, 0.0, 1.0));
17 for (int i = 0; i < 3; ++i) {
18 gl_Position = gl_in[i].gl_Position;
19 gBaryCoord = lut[i];
20 gColor = vColor[i];
21 EmitVertex();
22 }
23 }
■ vectors:
2 ℓ: unit vector vector in direction from point on surface to light source
2 n: unit normal at point on surface
2 v: unit vector in direction from point on surface to viewer
2 r : unit vector in direction that perfectly reflected light ray would take from
Light Source
n
r
Eye
v θ θ −ℓ
Surface
1 #version 330
2
3 in vec3 vColor; // input color
4
5 out vec4 fColor; // output color
6
7 void main() {
8 fColor = vec4(vColor, 1.0);
9 }
1 #version 330
2
3 in vec3 aPosition; // position vertex attribute
4 in vec3 aNormal; // normal vertex attribute
5
6 out vec3 vPosition; // output position (interpolated)
7 out vec3 vNormal; // output normal (interpolated)
8
9 uniform mat4 uModelViewMatrix; // modelview matrix
10 uniform mat3 uNormalMatrix; // normal transformation matrix
11 uniform mat4 uModelViewProjMatrix;
12 // modelview-projection matrix product
13
14 void main() {
15 vNormal = normalize(uNormalMatrix * aNormal);
16 vPosition = vec3(uModelViewMatrix * vec4(aPosition, 1.0));
17 gl_Position = uModelViewProjMatrix * vec4(aPosition, 1.0);
18 }
1 #version 330
2
3 in vec3 vNormal; // input normal
4 in vec3 vPosition; // input position
5
6 out vec4 fColor; // output color
7
8 struct LightSourceParams {
9 vec4 position; // position
10 vec3 ambient; // ambient component
11 vec3 diffuse; // diffuse component
12 vec3 specular; // specular component
13 };
14 uniform LightSourceParams uLight; // light parameters
15
16 struct MaterialParams {
17 vec3 ambient; // ambient reflectance
18 vec3 diffuse; // diffuse reflectance
19 vec3 specular; // specular reflectance
20 float shininess; // specular exponent
21 };
22 uniform MaterialParams uMaterial; // material parameters
References
Other Libraries
■ Eigen
2 C++ library for linear algebra
2 web site: http://eigen.tuxfamily.org
■ Lapack++
2 C++ library for high-performance linear-algebra computations
2 C++ wrapper for LAPACK and BLAS
2 web site: http://lapackpp.sourceforge.net
■ Armadillo
2 C++ library for linear algebra
2 web site: http://arma.sourceforge.net
■ GNU Scientific Library
2 C library for numerical analysis
2 web site: http://www.gnu.org/software/gsl
■ GNU Multiprecision Library
2 C library for arbitrary-precision arithmetic
2 web site: http://gmplib.org
Programming
■ Do not unnecessarily complicate code. Use the simplest solution that will
meet the needs of the problem at hand.
■ Do not impose bogus limitations. If a more general case can be handled
without complicating the code and this more general case is likely to be
helpful to handle, then handle this case.
■ Do not unnecessarily optimize code. Highly optimized code is often much
less readable. Also, highly optimized code is often more difficult to write
correctly (i.e., without bugs). Do not write grossly inefficient code that is
obviously going to cause performance problems, but do not optimize
things beyond avoiding gross inefficiencies that you know will cause
performance problems.
■ The single most important thing when writing code is that it does the job it
was intended to do correctly. That is, there should not be any bugs.
■ Test your code. If you do not spend as much time testing your code as you
do writing it, you are likely not doing enough testing.
■ Tests should exercise as much of the code as possible (i.e., provide good
code coverage).
■ Design and structure your code so that it is easy to test. In other words,
testing should be considered during design.
■ Your code will have bugs. Design your code so that it will help you to
isolate bugs. Use assertions. Use preconditions and postconditions.
■ Design your code so that is modular and can be written and tested in
pieces. The first testing of the software should never be testing the entire
software as a whole.
■ Often in order to adequately test code, one has to write separate
specialized test code.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1905
Code Examples
■ subscripting operator for 1-D array class:
template <class T>
const T& Array_1<T>::operator[](int i) const {
// Precondition: index is in allowable range
assert(i >= 0 && i < data_.size());
return data_[i];
}
■ function taking pointer parameter:
int stringLength(const char* ptr) {
// Precondition: pointer is not null
assert(ptr);
// Code to compute and return string length.
// ...
}
■ function that modifies highly complicated data structure:
void modifyDataStructure(Type& dataStructure) {
// Precondition: data structure is in valid state
assert(isDataStructureValid(dataStructure));
// Complicated code to update data structure.
// ...
// Postcondition: data structure is in valid state
assert(isDataStructureValid(dataStructure));
}
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1906
Section 6.2
Algorithms
f (n)
c1 g(n)
n
n0
■ f ∈ Θ(g)
■ for n ≥ n0 , f (n) is lower bounded by c1 g(n) and upper bounded by
c2 g(n)
■ asymptotically, f grows at same rate as g to within constant factor
cg(n)
f (n)
n
n0
■ f ∈ O(g)
■ for n ≥ n0 , f (n) is upper bounded by cg(n)
■ asymptotically, f grows at rate no greater than that of g to within constant
factor
f (n)
cg(n)
n
n0
■ f ∈ Ω(g)
■ for n ≥ n0 , f (n) lower bounded by cg(n)
■ asymptotically, f grows at rate no less than that of g to within constant
factor
■ small-oh (o) notation: for function g, o(g) denotes set of all functions f
such that, for any positive constant c, positive constant n0 exists such that
0 ≤ f (n) < cg(n) for all n ≥ n0
■ functions in o(g) grow asymptotically at strictly lesser rate than g (to
within constant factor)
■ used to provide upper bound on function that is not asymptotically tight
■ f ∈ o(g) implies that f (n) becomes insignificant relative to g(n) as n
f (n)
becomes arbitrarily large (i.e., limn→∞ g(n) = 0)
■ examples:
2 f (n) = 3n3 + 2n + 1; f ∈ o(n5 ) and f ∈ o(n4 ) but f ̸∈ o(n3 )
2 f (n) = 2n2 ; f ̸∈ o(n2 ) but f ∈ O(n2 )
2 f (n) = ∑d i
i=0 ai n where {ai } are constants and ad > 0;
f ∈ o(n ) and f ∈ o(nd+2 ) but f ̸∈ o(nd ) and f ̸∈ o(nd−1 )
d+1
■ sum of functions:
2 if f ∈ Θ(g) and f ∈ Θ(g), then f + f ∈ Θ(g)
1 2 1 2
2 if f 1 ∈ O(g) and f 2 ∈ O(g), then f 1 + f 2 ∈ O(g)
2 if f ∈ Ω(g) and f ∈ Ω(g), then f + f ∈ Ω(g)
1 2 1 2
■ multiplication by constant:
2 for all positive functions f and all positive constants a, a f ∈ Θ( f ),
a f ∈ O( f ), and a f ∈ Ω( f )
■ product of functions:
2 for all positive functions f1 , f2 , g1 , g2 , if f1 ∈ Θ(g1 ) and f2 ∈ Θ(g2 ), then
f1 f2 ∈ Θ(g1 g2 )
2 for all positive functions f1 , f2 , g1 , g2 , if f1 ∈ O(g1 ) and f2 ∈ O(g2 ), then
f1 f2 ∈ O(g1 g2 )
2 for all positive functions f1 , f2 , g1 , g2 , if f1 ∈ Ω(g1 ) and f2 ∈ Ω(g2 ), then
f1 f2 ∈ Ω(g1 g2 )
■ examples:
2 if f ∈ Θ(n), then n f (n) ∈ Θ(n2 )
2 if f and g are positive functions in Θ(1), then f + g ∈ Θ(1)
■ log2 n ∈ Θ(logb n) for all b > 1 (i.e., base of logarithm does not impact
asymptotic analysis)
Name Complexity
constant O(1)
logarithmic O(log n)
fractional power O(nc ), c ∈ (0, 1)
linear O(n)
log-linear O(n log n)
quadratic O(n2 )
cubic O(n3 )
exponential O(an )
factorial O(n!)
n
double exponential O(ab )
References
Data Structures
■ abstract data type (ADT) is model for data type where behavior
specified from point of view of user of type (i.e., with implementation
details hidden)
■ ADT specifies:
2 general nature of entity represented by type
2 set of allowable states/values that type can assume
2 set of operations that can be performed on type
2 any preconditions or postconditions for operations
■ often, ADT also provides complexity guarantees (e.g., time or space
complexity guarantees for various operations)
■ for example, (generic) integer type is ADT:
2 can assume integer values
2 provides basic arithmetic operations, relational operations, and so on
2 particular representation used for integers not specified by ADT
■ in contrast to ADT, concrete (i.e., non-abstract) data type provides very
specific details as to how type is implemented
std::unordered_map, std::unordered_multimap
2 boost::intrusive::slist, boost::intrusive::list
■ list ADT is ADT that stores countable number of ordered values, where
same value may occur more than once
■ operations for list ADT include:
2 clear: remove all elements from list
2 is empty: test if list empty
2 size: query number of elements in list
2 insert: insert element in list
2 remove: remove element from list
■ operations for traversing elements in list (which are often provided via
iterator ADT) include:
2 successor: get next element in list
2 predecessor (optional): get previous element in list
■ examples of realizations of list ADT:
2 std::vector, std::forward_list, and std::list
■ advantages:
2 elements stored contiguously in memory (which is cache friendly)
2 no per-element storage overhead
2 can insert at end of list in amortized O(1) time
2 can remove at end of list in O(1) time
2 can access element in any position in O(1) time
2 (random-access) iterator has storage cost of one pointer
■ disadvantages:
2 cannot insert or remove at start or arbitrary position in O(1) time
2 if capacity of array exceeded, memory reallocation and copying required
2 if array can be reallocated, insert at end can only at best guarantee
amortized (not worst-case) O(1) time
2 if array reallocated, element references invalidated
■ useful when insertion and removal only performed at end of list and stable
references to elements not needed
Iterator Iterator
(element) (end position)
node_ node_ 0
tail_ tail_
■ advantages:
2 no capacity-exceeded problem like in array case
2 stable references to elements
2 can insert or remove at arbitrary position in O(1) time
2 can find successor and predecessor in O(1) time
2 can efficiently iterate both forwards and backwards over elements in list
■ disadvantages:
2 elements not stored contiguously in memory
2 per-element storage overhead (2 pointers)
2 relative to singly-linked list, has greater per-element storage overhead
(1 additional pointer for predecessor)
2 iterator storage cost is more than single pointer (i.e., 2 pointers)
■ most useful for lists where insertion and removal can happen anywhere in
list
Iterator Iterator
(element) (end position)
node_ node_
■ stack ADT is ADT for container where elements can only be inserted or
removed in last-in first-out (LIFO) order
■ can only insert and remove elements at top of stack
■ operations provided by stack ADT:
2 clear: remove all elements from stack
2 is empty: test if stack is empty
2 top: access element at top of stack (without removing)
2 push: add element to top of stack
2 pop: remove element from top of stack
■ stack overflow: attempting to perform push operation when insufficient
space available for element being added
■ stack underflow: attempting to perform pop operation when stack empty
■ example realizations of stack ADT:
2 std::stack
2 boost::lockfree::stack
Stack Array of T
x0
start_
x1
end_ ..
.
ptr_ xn−1
..
.
■ advantages:
2 elements stored contiguously in memory
2 no per-element storage overhead
■ disadvantages:
2 if capacity of array exceeded, must reallocate and copy
2 if array grown, can only guarantee amortized (not worst-case) O(1) time for
push
2 if array reallocated, elements references are invalidated
■ advantages:
2 no capacity-exceeded problem as in array case
2 can perform push operation in O(1) time in worst case
2 element references are stable
■ disadvantages:
2 element data not contiguous in memory
2 has per-element storage overhead (i.e., 1 pointer for successor)
2 relative to array-based implementation, requires more space
Queue Array of T
start_
end_ ..
.
head_
x0
tail_
x1
size_ n ..
.
xn−1
..
.
Queue Block
map_
size_ 6 x0
x1
cur_
first_ Map
start_ Block
last_ 0 x2
0 x3
block_
x4
x5
cur_
0
first_
finish_ Block
last_
x6
block_ x7
■ advantages:
2 elements never change their location so pointers and references to
elements are stable
■ disadvantages:
2 although each individual block holding element data is contiguous, blocks
not contiguous
2 although elements are never relocated by insertions and removals, iterators
can be invalidated
■ similar data structure used in some implementations of std::deque
Iterator
node_
tail_
■ advantages:
2 enqueue and dequeue operations can be performed in O(1) time
2 stable element references
■ disadvantages:
2 elements not stored contiguously in memory
■ could use doubly-linked list with sentinel node in order to facilitate more
efficient iterator
■ example:
A 2 A is root node
2 B is child of A
B C D
2 A is parent of B
E F G H 2 C and D are siblings of B
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 1984
Tree Terminology
■ path of length k in tree is sequence of k + 1 nodes n0 , n1 , . . . , nk where ni
is parent of ni+1
■ node q said to be ancestor of node n if q is on path from root node to n
■ node q is said to be descendant of node n if q on path from n to leaf
■ every node is both ancestor and descendant of itself
■ node q said to be proper ancestor of n if ancestor of, and distinct from, n
■ node q is said to be proper descendant of n if q is descendant of, and
distinct from, n
■ example:
A 2 A, B, F is path of length 2
2 A and B are proper ancestors of E
B C D
2 E and F are proper descendants of B
E F G H 2 B is ancestor and descendant of B
■ example:
2 tree consisting of nodes B, E , and F
A is subtree associated with node B
2 degree of node B is 2
B C D
2 degree of tree is 3
■ example:
A
2 depths of nodes C and E are 1 and 2,
respectively
■ example:
A
2 weights of nodes B and C are 2 and
B C D 0, respectively
2 weight of tree is 5
E F G H
■ example:
A
2 preorder traversal visits nodes in
order: A, B, E , F , C, D, G, H
B C D
2 postorder traversal visits nodes in
order: E , F , B, C, G, H , D, A
E F G H
root_ parent_ 0
size_ 6 child_
sibling_ 0
elem_
Node Node
parent_ parent_
child_ 0 child_ 0
sibling_ sibling_ 0
elem_ elem_
■ example:
2 root node is A
A
2 left child of A is B
B C 2 right child of A is C
D E F
2 left subtree of A is tree consisting of nodes B, D, and E
2 right subtree of A is tree consisting of nodes C and F
■ binary tree said to be perfect (or full) if each internal node has exactly
two children (which results in all leaves being at same level)
■ binary tree said to be complete if perfect except possibly for deepest level
which must be filled from left to right
■ perfect implies complete
Perfect Complete
Perfectly Balanced
Strictly Balanced Height Balanced
■ preorder traversal: visit node, then left subtree, then right subtree
■ postorder traversal: visit left subtree, then right subtree, then node
■ level-order traversal: visit nodes from left to right within level from top
downwards
■ one additional traversal order for binary trees: in order
■ in-order traversal: visit left subtree, then node, then right subtree
■ example:
A 2 preorder traversal order: A, B, D, E , C, F
2 postorder traversal order: D, E , B, F , C, A
B C
2 inorder traversal order: D, B, E , A, C, F
D E F
2 level order traversal order: A, B, C, D, E , F
root_ parent_ 0
size_ 5 left_
right_
elem_
Node Node
parent_ parent_
left_ left_ 0
right_ right_ 0
elem_ elem_
Node Node
parent_ parent_
left_ 0 left_ 0
right_ 0 right_ 0
elem_ elem_
■ advantages:
2 can handle case of tree that is not complete without gross memory
inefficiency
2 can provide stable element references
■ disadvantages:
2 has per-element storage overhead (3 pointers: 1 for parent, 1 for first child,
and 1 for second child or next sibling)
2 element data not contiguous
■ advantages:
2 memory efficient: no per-element storage overhead (i.e., no memory cost
for representing connectivity of nodes in tree)
2 cache efficient: element data stored contiguously in memory
■ disadvantages:
2 can only handle complete trees
2 although could generalize this approach to handle non-complete tree, would
be grossly inefficient in terms of memory usage
2 if array capacity exceeded, costly reallocation and copy required
2 if array reallocation occurs, cannot provide stable references to elements
■ array implementation should be preferred for complete trees (unless
inability to guarantee stable element references is problematic)
■ binary tree is said to have binary search tree property if, for each node
node n with key k, following holds:
2 every key in left subtree of n is less than or equal to k; and
2 every key in right subtree of n is greater than or equal to k
20 60
10 30 50 70
5 25 35 45 80
■ tree said to have heap property if, for each node n in tree, following
holds:
2 key of n is greater than or equal to key of each descendant of n
70 60
55 45 35 42
10 20 30 15 16
Hash Tables
Index Slot
0000
0001 0019910001
0002 5919870002
0003
..
.
9997 1212009997
9998
9999 1122339999
■ with linear probing, probe sequence starts at hash value of key and then
proceeds as necessary sequentially, wrapping around to beginning of
table when end of table reached
■ ith value in probe sequence for key k given by h(k, i) = (h′ (k) + i) mod m,
where h′ is hash function
■ suffers from primary clustering, where colliding elements clump together
causing future collisions to generate longer sequence of probes
■ expected
(︂ number
)︂ of probes for insertion or unsuccessful search is
1 1
2 1 + (1−α)2
0 1 2 3 4 5 6 7 8 9 10 11 12
15 18
k h(k)
0 1 2 3 4 5 6 7 8 9 10 11 12
18 5
15 18 23
15 2
23 10
0 1 2 3 4 5 6 7 8 9 10 11 12
31 5
15 18 31 23
44 5
9 9
0 1 2 3 4 5 6 7 8 9 10 11 12
15 18 31 44 23
0 1 2 3 4 5 6 7 8 9 10 11 12
15 18 31 44 9 23
0 1 2 3 4 5 6 7 8 9 10 11 12
15 18
0 1 2 3 4 5 6 7 8 9 10 11 12 k h(k)
15 18 23 18 5
15 2
0 1 2 3 4 5 6 7 8 9 10 11 12 23 10
15 18 31 23 31 5
44 5
0 1 2 3 4 5 6 7 8 9 10 11 12 9 9
15 18 31 44 23
0 1 2 3 4 5 6 7 8 9 10 11 12
9 15 18 31 44 23
0 1 2 3 4 5 6 7 8 9 10 11 12
15 18
0 1 2 3 4 5 6 7 8 9 10 11 12
k h(k) d(k)
15 18 23
18 5 3
15 2 6
0 1 2 3 4 5 6 7 8 9 10 11 12 23 10 5
15 18 31 23 31 5 4
44 5 5
0 1 2 3 4 5 6 7 8 9 10 11 12 9 9 5
15 18 44 31 23
0 1 2 3 4 5 6 7 8 9 10 11 12
9 15 18 44 31 23
■ if keep adding elements to hash table, eventually size of table will need to
be increased, due to loading factor becoming too large (for good
performance or correct behavior)
■ rehashing: rebuilding hash table with different number of slots
■ typical threshold for load factor α for rehashing:
2 1 for chaining
1
2
2 for open addressing
2 boost::intrusive::unordered_set and
boost::intrusive::unordered_multiset
2 boost::intrusive::set and boost::intrusive::multiset
■ map (or associative array) ADT is container that stores pairs each
consisting of key and value, where keys are unique
■ each element in map consists of key and value
■ operations provided by map ADT include:
2 clear: remove all elements from map
2 is empty: test if map is empty
2 size: query number of elements in map
2 insert: insert element in map
2 remove: remove element from map
2 find: locate element in map if present based on its key
■ multimap ADT similar to map ADT except that keys need not be unique
■ example realizations of map/multimap ADT:
2 std::map and std::multimap
2 std::unordered_map and std::unordered_multimap
2 boost::intrusive::set and boost::intrusive::multiset
■ some C++ standard library implementations use red-black trees for types
that provide binary search tree functionality (e.g., std::set and
std::map)
■ example realizations of red-black trees:
2 boost::intrusive::rbtree, boost::intrusive::set, and
boost::intrusive::multiset
■ example of red-black tree (where red nodes are shaded gray):
40
20 60
10 30 50 70
5 45 55 65 75
42 48
■ since AVL trees more rigidly balanced than red-black trees, search
operations typically faster in AVL tree
■ insertion and removal operations typically slower in AVL tree than in
red-black tree, due to more work being required for tree re-balancing
■ example realizations of AVL trees:
2 boost::intrusive::avltree, boost::intrusive::avl_set, and
boost::intrusive::avl_multiset
■ example of AVL tree:
40
(+1)
20 60
(−1) (+1)
10 30 50 70
(−1) (0) (−1) (−1)
5 45 65 75
(0) (0) (0) (0)
62 66
(0) (0)
boost::intrusive::treap_multiset
■ splay tree is self-adjusting binary search tree with property that searches
for more frequently accessed elements can be performed more quickly
■ splay tree keeps more recently accessed elements closer to root
■ caching effect comes at cost of tree rebalancing being required each time
search is performed
■ significant disadvantage of splay tree is that height of tree can become
linear in number of elements
■ in worst case, insertion, removal, and search operations take amortized
O(log n) time
■ example realizations of splay trees:
2 boost::intrusive::splay_tree, boost::intrusive::splay_set,
and boost::intrusive::splay_multiset
boost::intrusive::sg_multiset
Priority Queues
■ priority queue ADT is ADT similar to queue except that each element on
queue also has corresponding priority
■ element at front of queue is always element with highest priority
■ operations provided by priority queue ADT include:
2 front: access element at front of queue (i.e., element with highest priority)
2 insert: insert element in queue with specified priority
2 remove: remove element from front of queue (i.e.. element with highest
priority)
2 update priority (optional): update priority of element in queue
■ if priority queue has stability property, elements with equal priority will
be removed in FIFO order
■ examples of realization of priority queue ADT:
2 std::priority_queue,
2 boost::heap::priority_queue and boost::heap::fibonacci_heap
Graphs
B C D B C D
E E
Undirected Directed
struct Face {
int vertexIndexes[3]; // indexes of vertices of triangle
};
Vertex vertices[numVertices]; // vertex array
Face triangles[numTriangles]; // triangle array
f1
v3
f2 = (1, 1, 1)
f0
v0 v1
= (−1, −1, 1) = (1, −1, −1)
Vertices
Faces
Array Array
Array Array
Index Element
Index Element
0 (-1,-1,1)
0 0, 1, 3
1 (1,-1,-1)
1 1, 2, 3
2 (-1,1,-1)
2 0, 3, 2
3 (1,1,1)
■ described in:
K. Weiler. Edge-based data structures for solid modeling in
curved-surface environments. IEEE Computer Graphics and Applications,
5(1):21–40, Jan. 1985.
■ every edge represented as pair of directed edges, each called half-edge
■ 6 pointers plus 2 bits (i.e., 2 one-bit integers) per edge
■ used in Computational Geometry Algorithms Library (CGAL)
■ representing edges in terms of directed line segments often
advantageous in algorithms
e[0].next
e[0].left e[1].term
e[0]
e[1] vertex
e[0].term edge
e[1].left
face
e[1].next
struct HalfEdge {
int index; // index of half-edge in parent edge
HalfEdge* next; // next CCW half-edge around left face
Vertex* term; // terminal vertex
Face* left; // left face
};
struct Edge {
HalfEdge e[2]; // pair of symmetric half-edges
};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 2050
Quad-Edge Data Structure
■ proposed in:
L. Guibas and J. Stolfi. Primitives for the manipulation of general
subdivisions and the computation of Voronoi diagrams. ACM Transactions
on Graphics, 4(2):74–123, Apr. 1985.
■ simultaneously represents graph and its dual
■ each edge belongs to four circular singly-linked lists corresponding to two
vertices and two faces incident to edge
■ vertex/face represented by ring of quad-edges
■ 8 pointers plus 4 two-bit integers per edge
■ used in various research software available on Internet (e.g., Scape
terrain-simplification software, Dani Lischinski’s constrained DT software)
e[1].data
e[1].next
e[2].next
e[1]
e[0].data
e[0]
vertex
e[2]
e[2].data e[3]
e[0].next
edge
e[3].next
e[3].data
face
struct QuadEdge {
int index; // index of quad-edge in parent edge
QuadEdge* next; // next CCW quad-edge with same origin
void* data; // face or vertex
};
struct Edge {
QuadEdge* e[4]; // four quad-edges of edge
};
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 2052
Object File Format (OFF)
v3 v2 OFF
= (−1, 1, 0) = (1, 1, 0) 5 4 0
-1 -1 0
1 -1 0
1 1 0
-1 1 0
0 0 1
v4 3 0 1 4
= (0, 0, 1) 3 1 2 4
3 2 3 4
v0 v1 3 0 4 3
= (−1, −1, 0) = (1, −1, 0)
Corresponding
Mesh
OFF File
OFF
9 4 0
v6 v5 v4 -1 -1 -1
= (−1, 1, −1) = (0, 1, 0) = (1, 1, −1) 0 -1 0
1 -1 -1
1 0 0
1 1 -1
v7 v3 0 1 0
= (−1, 0, 0) v8 = (1, 0, 0) -1 1 -1
= (0, 0, 1) -1 0 0
0 0 1
4 0 1 8 7
v0 v1 v2 4 1 2 3 8
= (−1, −1, −1) = (0, −1, 0) = (1, −1, −1) 4 8 3 4 5
4 7 8 5 6
Mesh
Corresponding
OFF File
Intrusive Containers
■ typically, intrusive containers not copyable (and often not movable as well)
since such containers do not directly perform memory allocation
■ analyzing thread safety of program using intrusive containers often more
difficult since container contents can be modified without going through
container interface
.. .. ..
. . .
Iterator
node_
tail_
■ node pointer and value pointer are equivalent (i.e., pointers to next and
previous nodes have type T*)
■ storage cost of iterator is two pointers (but one pointer would be more
desirable)
■ iterator state requires pointer to list tail pointer in order to handle case of
decrementing end iterator (which has null node pointer)
■ implementation does not require any non-portable constructs
Iterator
node_
multiset/multimap)
2 boost::intrusive_ptr (intrusive reference-counted smart pointer)
Miscellany
0 1 2 M−1 0 1 2 M−1
0 0
··· ···
1 1
··· ···
2 2
··· ···
.. .. .. . . .. .. .. .. . . ..
. . . . . . . . . .
··· ···
N −1 N −1
References
Finite-Precision Arithmetic
1 #include <iostream>
2 #include <iomanip>
3 #include <limits>
4 #include <boost/io/ios_state.hpp>
5
6 template <class T> void func(T x) {
7 boost::io::ios_flags_saver saver(std::cout);
8 std::cout << x << ’ ’ <<
9 std::setprecision(std::numeric_limits<T>::digits10) << x << ’ ’ <<
10 std::setprecision(std::numeric_limits<T>::max_digits10) << x << ’ ’ <<
11 std::hexfloat << x << ’\n’;
12 }
13
14 int main() {
15 func(0.1f);
16 func(0.1);
17 func(0.1L);
18 }
19
20 /* example output:
21 0.1 0.1 0.100000001 0x1.99999ap-4
22 0.1 0.1 0.10000000000000001 0x1.999999999999ap-4
23 0.1 0.1 0.100000000000000000001 0xc.ccccccccccccccdp-7
24 */
■ Note: All numbers are base 10, unless explicitly indicated otherwise.
■ What is the representation of 13 in base 3?
1
3 = 0.3 = 0.13
1
■ What is the representation of 10 in base 2?
1
10 = 0.1 = 0.000112
■ A number may have a representation with a finite number of non-zero
digits in one particular number base but not in another.
■ Therefore, when a value must be represented with a limited number of
significant digits, the number base matters (i.e., affects the approximation
error).
1
■ For example, in base 2, 10 cannot be represented exactly using only a
finite number of significant digits.
0.000112 = 0.09375
0.0001100112 = 0.099609375
...
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 2083
Finite-Precision Number Representations
■ single format:
1 8 23
s e f
MSB LSB MSB LSB
■ double format:
1 11 52
s e f
MSB LSB MSB LSB
■ summary of encodings:
Case Exponent Fraction Value
Normal Emin ≤ E ≤ Emax — (−1)s 2E (1 + f )
Denormal E = Emin − 1 f ̸= 0 (−1)s 2Emin f
Zero E = Emin − 1 f =0 (−1)s 0
Infinity E = Emax + 1 f =0 (−1)s ∞
NaN E = Emax + 1 f ̸= 0 NaN
Interval Arithmetic
■ addition:
A + B = [a1 , a2 ] + [b1 , b2 ] = [a1 + b1 , a2 + b2 ]
■ negation:
−B = −[b1 , b2 ] = [−b2 , −b1 ]
■ formula for negation follows from fact that:
2 x ≥ b ⇒ −x ≤ −b and
1 1
2 x ≤ b ⇒ −x ≥ −b
2 2
■ subtraction:
A − B = [a1 , a2 ] − [b1 , b2 ] = [a1 − b2 , a2 − b1 ]
■ formula for subtraction follows from combining addition and negation
■ must ensure that rounding does not cause interval to no longer bracket
result that would be obtained by (exact) real interval arithmetic
■ need to select shortest interval that contains result that would be obtained
from (exact) real interval arithmetic
■ lower bound of result must be computed with rounding downwards
■ upper bound of result must be computed with rounding upwards
■ using rounding in this way ensures that resulting interval will bracket
idealized (exact real) interval
△
A − B = [a1 , a2 ] − [b1 , b2 ] = [a1 b2 , a2 △
− b ]
−
1
A · B = [a1 , a2 ] · [b1 , b2 ]
b2 ≤ 0 b1 < 0 < b2 b1 ≥ 0
△ △ △
a2 ≤ 0 [a2 b2 , a1 △
× b ] [a1 b2 , a1 △ × b ] [a1 b2 , a2 △
× b ]
× × ×
1 1 1
△ △ △ △
a1 < 0 < a2 [a2 b1 , a1 △b1 ] [min{a1 b2 , a2 b1 }, [a1 b2 , a2 △
× b ]
× × × ×
×
2
max{a1 △ × b ,a △
1 2 × b2 ]}
△ △ △
a1 ≥ 0 [a2 b1 , a1 △b2 ] [a2 b1 , a2 △ × b ] [a1 b1 , a2 △
× b ]
× × ×
×
2 2
A+B
(−∞, b2 ] [b1 , b2 ] [b1 , +∞) (−∞, +∞)
(−∞, a2 ] (−∞, a2 △b2 ] (−∞, a2 △
+ + b ]
2 (−∞, +∞) (−∞, +∞)
△ △
[a1 , a2 ] (−∞, a2 △+ b ] [a b , a △+ b ] [a1 b1 , +∞) (−∞, +∞)
+ +
2 1 1 2 2
△ △
[a1 , +∞) (−∞, +∞) [a1 b1 , +∞) [a1 b1 , +∞) (−∞, +∞)
+ +
(−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞)
A−B
(−∞, b2 ] [b1 , b2 ] [b1 , +∞) (−∞, +∞)
(−∞, a2 ] (−∞, +∞) (−∞, a2 △ − b ]
1 (−∞, a2 △− b ]
1 (−∞, +∞)
△ △
[a1 , a2 ] [a1 b2 , +∞) [a1 b2 , a2 △ − b ] (−∞, a2 △− b ] (−∞, +∞)
− −
1 1
△ △
[a1 , +∞) [a1 b2 , +∞) [a1 b2 , +∞) (−∞, +∞) (−∞, +∞)
− −
(−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞)
A·B
[b1 , b2 ] [b1 , b2 ] [b1 , b2 ]
[0, 0]
b2 ≤ 0 b1 < 0 < b2 b1 ≥ 0
[a1 , a2 ] △ △ △
[a2 b2 , a1 △
× b ] [a1 b2 , a1 △
× b ] [a1 b2 , a2 △
× b ] [0, 0]
× × ×
1 1 1
a2 ≤ 0
△ △
[a1 , a2 ] [min{a1 b2 , a2 b1 },
× ×
△ △
[a2 b1 , a1 △
× b ] [a1 b2 , a2 △
× b ] [0, 0]
× ×
1 2
a1 < 0 < a2 × b ,a △
max{a1 △ 1 2 × b2 }]
[a1 , a2 ] △ △ △
[a2 b1 , a1 △
× b ] [a2 b1 , a2 △
× b ] [a1 b1 , a2 △
× b ] [0, 0]
× × ×
2 2 2
a1 ≥ 0
[0, 0] [0, 0] [0, 0] [0, 0] [0, 0]
(−∞, a2 ] △
[a2 b2 , +∞) (−∞, +∞) (−∞, a2 △
× b ] [0, 0]
×
1
a2 ≤ 0
(−∞, a2 ] △
[a2 b1 , +∞) (−∞, +∞) (−∞, a2 △
× b ] [0, 0]
×
2
a2 ≥ 0
[a1 , +∞) △
(−∞, a1 △
× b ] (−∞, +∞) [a1 b2 , +∞) [0, 0]
×
1
a1 ≤ 0
[a1 , +∞) △
(−∞, a1 △
× b ] (−∞, +∞) [a1 b1 , +∞) [0, 0]
×
2
a1 ≥ 0
(−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞) [0, 0]
A·B
(−∞, b2 ] (−∞, b2 ] [b1 , +∞) [b1 , +∞)
(−∞, +∞)
b2 ≤ 0 b2 ≥ 0 b1 ≤ 0 b1 ≥ 0
[a1 , a2 ] △ △
[a2 b2 , +∞) [a1 b2 , +∞) (−∞, a1 △
× b ] (−∞, a2 △
× b ] (−∞, +∞)
× ×
1 1
a2 ≤ 0
[a1 , a2 ]
(−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞)
a1 < 0 < a2
[a1 , a2 ] △ △
(−∞, a1 △
× b ] (−∞, a2 △
× b ] [a2 b1 , +∞) [a1 b1 , +∞) (−∞, +∞)
× ×
2 2
a1 ≥ 0
[0, 0] [0, 0] [0, 0] [0, 0] [0, 0] [0, 0]
(−∞, a2 ] △
[a2 b2 , +∞) (−∞, +∞) (−∞, +∞) (−∞, a2 △
× b ] (−∞, +∞)
×
1
a2 ≤ 0
(−∞, a2 ]
(−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞)
a2 ≥ 0
[a1 , +∞)
(−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞)
a1 ≤ 0
[a1 , +∞) △
(−∞, a1 △
× b ] (−∞, +∞) (−∞, +∞) [a1 b1 , +∞) (−∞, +∞)
×
2
a1 ≥ 0
(−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞)
A/B, 0 ̸∈ B
[b1 , b2 ] [b1 , b2 ] (−∞, b2 ] [b1 , +∞)
b2 < 0 b1 > 0 b2 < 0 b1 > 0
[a1 , a2 ] △ △ △
[a2 b1 , a1 △
÷
÷ b ]
2 [a2 b1 , a2 △
÷
÷ b ]
2 [0, a1 △
÷ b ]
2 [a1 b1 , 0]
÷
a2 ≤ 0
[a1 , a2 ] △ △ △ △
[a2 b2 , a1 △
÷
÷ b ]
2 [a1 b1 , a2 △
÷
÷ b ]
1 [a2 b2 , a1 △
÷
÷ b ]
2 [a1 b1 , a2 △
÷
÷ b ]
1
a1 < 0 < a2
[a1 , a2 ] △ △ △
[a2 b2 , a1 △
÷
÷ b ]
1 [a1 b2 , a2 △
÷
÷ b ]
1 [a2 b2 , 0]÷
[0, a2 △
÷ b ]
1
a1 ≥ 0
[0, 0] [0, 0] [0, 0] [0, 0] [0, 0]
(−∞, a2 ] △
[a2 b1 , +∞)
÷
(−∞, a2 △
÷ b ]
2 [0, +∞) (−∞, 0]
a2 ≤ 0
(−∞, a2 ] △ △
[a2 b2 , +∞)
÷
(−∞, a2 △
÷ b ]
1 [a2 b2 , +∞)
÷
(−∞, a2 △
÷ b ]
1
a2 ≥ 0
[a1 , +∞) △ △
(−∞, a1 △
÷ b ]
2 [a1 b1 , +∞)
÷
(−∞, a1 △
÷ b ]
2 [a1 b1 , +∞)
÷
a1 ≤ 0
[a1 , +∞) △
(−∞, a1 △
÷ b ]
1 (a1 b2 , +∞)
÷
(−∞, 0] [0, +∞)
a1 ≥ 0
(−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞)
A/B, 0 ∈ B
[b1 , b2 ] [b1 , b2 ] (−∞, b2 ] [b1 , +∞)
[0, 0] (−∞, +∞)
b1 < b2 = 0 0 = b1 < b2 b2 = 0 b1 = 0
[a1 , a2 ] △
0/ [a2 b1 , +∞) (−∞, a2 △
÷
÷ b ]
2 [0, +∞) (−∞, 0] (−∞, +∞)
a2 < 0
[a1 , a2 ]
(−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞)
a1 ≤ 0 ≤ a2
[a1 , a2 ] △
0/ (−∞, a1 △
÷ b ]
1 [a1 b2 , +∞) (−∞, 0]
÷
[0, +∞) (−∞, +∞)
a1 > 0
(−∞, a2 ] △
0/ [a2 b1 , +∞) (−∞, a2 △
÷
÷ b ]
2 [0, +∞) (−∞, 0] (−∞, +∞)
a2 < 0
(−∞, a2 ]
(−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞)
a2 > 0
[a1 , +∞)
(−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞)
a1 < 0
(a1 , +∞) △
0/ (−∞, a1 △
÷ b ]
1 [a1 b2 , +∞) (−∞, 0]
÷
[0, +∞) (−∞, +∞)
a1 > 0
(−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞) (−∞, +∞)
1 #include <iostream>
2 #include <cmath>
3 #include <cfenv>
4 #include <limits>
5
6 #pragma STDC FENV_ACCESS ON
7
8 int main() {
9 std::cout.precision(std::numeric_limits<double>::max_digits10);
10 int old_mode = std::fegetround();
11 int modes[] = {FE_TONEAREST, FE_TOWARDZERO, FE_UPWARD, FE_DOWNWARD};
12 for (auto mode : modes) {
13 if (std::fesetround(mode)) {abort();}
14 std::cout << std::sqrt(2.0) << ’\n’;
15 }
16 if (std::fesetround(old_mode)) {abort();}
17 std::cout << std::sqrt(2.0) << ’\n’;
18 }
19
20 /* Example output:
21 1.4142135623730951
22 1.4142135623730951
23 1.4142135623730952
24 1.4142135623730951
25 1.4142135623730951
26 */
e b
= (0, 2) = (2, 2)
d
= (1, 1)
a c
= (0, 0) = (2, 0)
[︂ 0 2 2 ]︂ [︁ −2 0 ]︁
■ orient2d(a, b, c) = det 020 = det 0 2 = −4 < 0; c is right of oriented
111
line through a and b
[︂ 0 2 1 ]︂ [︁ −1 1 ]︁
■ orient2d(a, b, d) = det 021 = det −1 1 = 0; d is on oriented line
111
through a and b
[︂ 0 2 0 ]︂
0 2
[︁ ]︁
■ orient2d(a, b, e) = det 022 = det −2 0 = 4 > 0; e is left of oriented
111
line through a and b
c b a c
c
b a
b
a
b
a
e
= (1, 1, 0)
b
a = (2, 0, 0)
= (0, 0, 0)
f
= (1, 1, −2)
[︃ 0 2 2 1 ]︃ [︂ −1 1 1
]︂
■ orient3d(a, b, c, d) = det 0021 = det −1 −1 1 = −8 < 0; d above
0002 −2 −2 −2
1111
oriented plane through a, b, and c
[︃ 0 2 2 1 ]︃ [︂ −1 1 1
]︂
■ orient3d(a, b, c, e) = det 0021 = det −1 −1 1 = 0; e lies in oriented
0000
1111 0 0 0
plane through a, b, and c
[︃ 0 2 2 1
]︃ [︂ −1 1 1
]︂
■ orient3d(a, b, c, f ) = det 002 1 = det −1 −1 1 = 8 > 0; f below
0 0 0 −2
111 1 2 2 2
oriented plane through a, b, and c
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 2125
Side-of-Oriented-Circle Test
c
d
positive negative
side side
a b
a b
= (0, 0) = (2, 0)
[︃ 0 2 0 1 ]︃
0 0 2 1
■ inCircle(a, b, c, d) = det 02 +02 22 +02 02 +22 12 +12 = 8 > 0; d on positive
1 1 1 1
side of oriented circle through a, b, and c (i.e., d inside circle)
[︃ 0 2 0 2 ]︃
0 0 2 2
■ inCircle(a, b, c, e) = det 02 +02 22 +02 02 +22 22 +22 = 0; e on oriented circle
1 1 1 1
through a, b, and c
[︃ 0 2 0 3 ]︃
0 0 2 3
■ inCircle(a, b, c, f ) = det 02 +02 22 +02 02 +22 32 +32 = −24 < 0; f on
1 1 1 1
negative side of oriented circle through a, b, and c (i.e., f outside circle)
d
v b
a c
■ given two line segments ab and cd and vector v, determine if, compared
to orientation of cd , orientation of ab is more close, less close, or equally
close to the orientation of v
■ can be determined from result of computation involving dot products
■ prefDir(a, b, c, d, v) = |d − c|2 ((b − a) · v)2 − |b − a|2 ((d − c) · v)2
■ if prefDir(a, b, c, d, v) is positive, negative, or zero, then compared to
orientation of cd , orientation of ab is more close, less close, or equally
close to orientation of v, respectively
u
d b = (2, 1)
= (0, 2) = (2, 2)
v w
a c = (1, 0) = (−1, 2)
= (0, 0) = (2, 0)
■ prefDir(a, b, c, d, u) =
|(−2, 2)|2 [(2, 2) · (2, 1)]2 − |(2, 2)|2 [(−2, 2) · (2, 1)]2 = 256 > 0; ab closer
than cd to direction of u
■ prefDir(a, b, c, d, v) =
|(−2, 2)|2 [(2, 2) · (1, 0)]2 − |(2, 2)|2 [(−2, 2) · (1, 0)]2 = 0; ab and cd
equally close to direction of v
■ prefDir(a, b, c, d, w) =
|(−2, 2)|2 [(2, 2) · (−1, 2)]2 − |(2, 2)|2 [(−2, 2) · (−1, 2)]2 = −256 < 0; cd
closer than ab to direction of w
Triangulation
Triangulation Invalid Triangulation
Delaunay
Triangulation Non-Delaunay
Delaunay Triangulation Triangulation Showing
Showing Circumcircles Violation of Circumcircle
Condition
vℓ e vj −→ vℓ
e′
vj
vi vi
■ number
(︁n)︁ n2 of different triangulations of n vertices upper bounded by
−n 2
2 = 2 , which is O(n )
■ all triangulations of set of vertices have same number of edges
■ every triangulation reachable from every other triangulation by edge flips
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 2134
Locally-Delaunay Test
c
b
e
d a
■ given flippable edge e in triangulation with incident faces abc and acd
whose vertices are specified in CCW order (and whose union is strictly
convex quadrilateral), determine if e is locally Delaunay
■ result can be determined using side-of-oriented-circle test
{︄
1 inCircle(a, b, c, d) ≤ 0
■ localDelaunay(a, b, c, d) =
0 inCircle(a, b, c, d) > 0
■ if localDelaunay(a, b, c, d) ̸= 0, edge e is locally Delaunay
■ if every flippable edge in triangulation is locally Delaunay, triangulation is
Delaunay
d a u
■ given flippable edge e in triangulation with incident faces abc and acd
whose vertices are specified in CCW order (and whose union is strictly
convex quadrilateral), determine if e is locally preferred-directions
Delaunay with first and second direction vectors u and v, respectively
(where u and v are nonzero and neither parallel nor orthogonal)
■ result can be determined using side-of-oriented-circle and
preferred-direction tests
⎧b, c, d, u, v)
■ α(a,
⎨1 prefDir(a, c, b, d, u) > 0
= 1 prefDir(a, c, b, d, u) = 0 and prefDir(a, c, b, d, v) > 0
0 otherwise
⎩
■ localPrefDirDelaunay(a,
⎧ b, c, d, u, v)
⎨1
⎪ inCircle(a, b, c, d) < 0
= 0 inCircle(a, b, c, d) > 0
⎪
⎩α(a, b, c, d, u, v) otherwise
■ if (and only if) localPrefDirDelaunay(a, b, c, d, u, v) ̸= 0, edge e is locally
preferred-directions Delaunay with first and second direction vectors u and
v, respectively
■ if every flippable edge in triangulation is locally preferred-directions
Delaunay, triangulation is preferred-directions Delaunay
e′ , mark e′ as not suspect, and mark any edges whose optimality might be
affected by flip of e as suspect
■ essentially, LOP simply keeps flipping (flippable) edges that are not
optimal until all edges are optimal
e −→ e′
3 3
2 2
1 1
0 x 0 x
0 1 2 3 0 1 2 3
y y
3 3
1 d0 = (1, 0) 1 d0 = (0, 1)
0 x 0 x
0 1 2 3 0 1 2 3
References
Cache-Efficient Code
Registers
Cache
(E.g., L1, L2, L3)
Increasing Latency
Memory
Increasing Capacity
(E.g., DRAM)
Decreasing Cost Per Byte
Secondary Storage
(E.g., Disk)
Tertiary Storage
(E.g., Tape)
■ memory block i can only be placed in cache block mod(i, N), where N is
number of blocks in cache
■ for example, if N = 8 (as above), memory block 10 can only be placed in
cache block mod(10, 8) = 2 [recall: mod(10102 , 23 ) = 0102 = 2]
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 2153
K -Way Set-Associative Cache Example
Memory Cache
Block B bytes Block B bytes
Number Number
0 0
Set 0
1 1
2 2
Set 1
3 3
4 4
Set 2
5 5
6 6
Set 3
7 7
8
9
10
11
..
.
M−1
■ memory block i can be placed in any of K cache blocks in set mod(i, S),
where S is number of sets
■ for example, if S = 4 and K = 2 (as above), memory block 10 can be
placed in any of cache blocks in set mod(10, 4) = 2 [recall:
mod(10102 , 22 ) = 102 = 2]
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 2154
Fully-Associative Cache Example
Memory Cache
Block B bytes Block B bytes
Number Number
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8
9
10
11
..
.
M−1
Cache Entries for ith Set (for K -Way Set Associative Cache)
■ need to determine if any entry matches tag and (if not fully associative)
index
■ first determine set in which block can be placed:
2 if not fully associative, determined by index
2 otherwise, cache only has one set
■ then look in this set for matching tag
■ if match found, cache hit; otherwise, cache miss
lower-level memory
2 write back: information written only to block in cache; modified cache block
written to main memory only when replaced
■ two basic write-miss policies:
1 write allocate (a.k.a. fetch on write): write miss brings block into cache,
■ P is page size
■ virtual address and physical address both decomposed into page number
and page offset
■ address translation only changes page number part of address
■ when virtual address translated to physical address, page offset does not
change
Virtual Physical
Address Address
CPU Virtual TLB Memory
Cache
TLB
Physical Page Number (24 bit) Physical Page Offset (12 bits) Cache
Cache Tags
Cache Result
Cache-Efficient Algorithms
■ before blocking:
1 // compute c := c + a b, where a, b, c are N-by-N
2 // matrices
3 template <class T, int N>
4 void naive_multiply(const T (&a)[N][N], const T (&b)[N][N],
5 T (&c)[N][N]) {
6 for (int i = 0; i < N; ++i) {
7 for (int j = 0; j < N; ++j) {
8 double s = 0;
9 for (int k = 0; k < N; ++k)
10 {s += a[i][k] * b[k][j];}
11 c[i][j] += s;
12 }
13 }
14 }
■ want to partition computation into blocks of size B×B, where B chosen so
that each block fits in cache
j j
k
N i N k N i
N N N
a b c
use each row of a N times for each row of a, update elements in c in
(once for each column of b) use each of N columns of b left-to-right top-to-bottom order
in succession
1 1
B B B
i kk B
i
a b c
use 1 × B row sliver use B × B block update successive elements of
B times N times in succession 1 × B row sliver
■ key idea is that block of b brought into cache, fully utilized, then discarded
■ innermost loop pair (i.e., for j and k) multiplies 1 × B sliver of a by B × B
block of b and accumulates result in 1 × B sliver of c
■ references to a have: good spatial locality, since elements accessed
consecutively in loop for k; and good temporal locality, since each sliver
accessed B times in succession in loop for j
■ references to b have good temporal locality, since entire block accessed N
times in succession in loop for i
■ references to c have good spatial locality since each element of sliver
written in succession in loop for j
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 2182
Cache-Aware Versus Cache-Oblivious Algorithms
Cache-Oblivious Algorithms
n
N
a0 a1 a2 a3 a4 a5 a6 a7 a8 a9
B B B
a0 a1 a2 a3 a4 a5 a6 a7 a8 a9
B B B B
■ cache block holds B array elements
■ consider scanning N elements of array in order (e.g., to compute sum or
minimum/maximum)
■ requires Θ(N) work (assuming work per element is O(1))
■ scanning N elements stored contiguously in memory incurs either
⌈N/B⌉ + 1 or ⌈N/B⌉ cache misses (i.e., Θ(N/B) cache misses)
■ may require one more than ⌈N/B⌉ cache misses due to arbitrary
alignment
■ cache oblivious and optimal (i.e., incurs only minimum number of cache
misses)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 2189
Array Reversal
1 2 3 4 5
a0 a1 a2 a3 a4 a5 a6 a7 a8 a9
B B B B
■ cache block holds B array elements
■ consider reversing elements of N -element array a
■ use two parallel scans, one from each end of array, and each step swaps
two corresponding elements
■ for i in 0, 1, . . . , ⌊N/2⌋ − 1, swap a[i] and a[N − 1 − i]
■ requires Θ(N) work (i.e., ⌊N/2⌋ swap operations)
■ assuming at least two blocks fit in cache, incurs either ⌈N/B⌉ + 1 or
⌈N/B⌉ cache misses (i.e., Θ(N/B) cache misses)
■ cache oblivious and optimal
j
m i n j
n m
a b
[︃ ]︃ [︃ ]︃T
b11 b12 a a
= 11 12
b21 b22 a21 a22
[︃ ]︃T [︃ ]︃T
a a
b11 b12 = 11 b21 b22 = 12
[︁ ]︁ [︁ ]︁
a21 a22
[︃ ]︃ [︃ ]︃T
b11 b12 a a
= 11 12
b21 b22 a21 a22
[︃ ]︃ [︃ ]︃
b11 [︁ ]︁T b12 [︁ ]︁T
= a11 a12 = a21 a22
b21 b22
⎡ ⎤
b11 b12 [︃ ]︃T
⎣b21 b22 ⎦ = a11 a12 a13
a21 a22 a23
b31 b32
[︃ ]︃ [︃ ]︃T [︃ ]︃T
b11 b12 a a a
= 11 12 b31 b32 = 13
[︁ ]︁
b21 b22 a21 a22 a23
[︃ ]︃T [︃ ]︃T
a a [︁ ]︁ [︁ ]︁T [︁ ]︁ [︁ ]︁T
b11 b12 = 11 b21 b22 = 12
[︁ ]︁ [︁ ]︁
b31 = a13 b32 = a23
a21 a22
k
m i n k m i
n p p
a b c
■ cache block holds L matrix elements
■ innermost loop (in which k varies) computes dot product of ith row of a
with kth column of b to yield (i, j)th element of c
■ second innermost loop (over j) changes column of b to use in dot product
with ith row of a (reusing ith row of a p times)
■ requires Θ(mnp) work, which is Θ(n3 ) in case of square matrices
■ assuming that row of a and column of b do not fit in cache simultaneously,
algorithm incurs Θ(mnp/L + mnp + mp/L) cache misses, which is Θ(n3 )
in case of square matrices
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 2200
Cache-Oblivious Matrix Multiplication
[︃ ]︃ [︃ ]︃ [︃ ]︃
c11 c12 a11 a12 b11 b12
+=
c21 c22 a21 a22 b21 b22
[︃ ]︃ [︃ ]︃
[︁ ]︁ [︁ ]︁ b11 b12 [︁ ]︁ [︁ ]︁ b11 b12
c11 c12 += a11 a12 c21 c22 += a21 a22
b21 b22 b21 b22
[︃ ]︃ [︃ ]︃ [︃ ]︃ [︃ ]︃
[︁ ]︁ b11 [︁ ]︁ b12 [︁ ]︁ b11 [︁ ]︁ b12
[c11 ]+= a11 a12 [c12 ]+= a11 a12 [c21 ]+= a21 a22 [c22 ]+= a21 a22
b21 b22 b21 b22
⎡ ⎤
]︃ b b12
a13 ⎣ 11
[︃ ]︃ [︃
c11 c12 a11 a12
+= b21 b22 ⎦
c21 c22 a21 a22 a23
b31 b32
[︃ ]︃ [︃ ]︃ [︃ ]︃ [︃ ]︃ [︃ ]︃
c11 c12 a11 a12 b11 b12 c11 c12 a13 [︁ ]︁
+= += b31 b32
c21 c22 a21 a22 b21 b22 c21 c22 a23
[︁ ]︁ [︁ ]︁
c11 c[︃12 += ]︃ c21 c[︃22 += ]︃ [︁ ]︁ [︁ ]︁
[︁ ]︁ b11 b12 [︁ ]︁ b11 b12 [︁ c11]︁ [︁ c12 += ]︁ [︁ c21]︁ [︁ c22 += ]︁
a11 a12 a21 a22 a13 b31 b32 a23 b31 b32
b21 b22 b21 b22
[︁ ]︁ [︁ ]︁ [︁ ]︁ [︁ ]︁
c11 += c12 += c21 += c22 += [︁ ]︁ [︁ ]︁ [︁ ]︁ [︁ ]︁
[︁ c11]︁ [︁ += ]︁ [︁ c12]︁ [︁ += ]︁ [︁ c21]︁ [︁ += ]︁ [︁ c22]︁ [︁ += ]︁
[︃ ]︃ [︃ ]︃ [︃ ]︃ [︃ ]︃
[︁ ]︁ b11 [︁ ]︁ b12 [︁ ]︁ b11 [︁ ]︁ b12
a11 a12 a11 a12 a21 a22 a21 a22 a13 b31 a13 b32 a23 b31 a23 b32
b21 b22 b21 b22
[︁ ]︁ [︁ ]︁ [︁ ]︁ [︁ ]︁ [︁ ]︁ [︁ ]︁ [︁ ]︁ [︁ ]︁
[︁ c11]︁ [︁ += ]︁ [︁ c11]︁ [︁ += ]︁ [︁ c12]︁ [︁ += ]︁ [︁ c12]︁ [︁ += ]︁ [︁ c21]︁ [︁ += ]︁ [︁ c21]︁ [︁ += ]︁ [︁ c22]︁ [︁ += ]︁ [︁ c22]︁ [︁ += ]︁
a11 b11 a12 b21 a11 b12 a12 b22 a21 b11 a22 b21 a21 b12 a22 b22
[︃ ]︃ [︃ ]︃ [︃ ]︃
c11 c12 a11 a12 b11 b12
=
c21 c22 a21 a22 b21 b22
[︃ ]︃ [︃ ]︃
[︁ ]︁ [︁ ]︁ b11 b12 [︁ ]︁ [︁ ]︁ b11 b12
c11 c12 = a11 a12 c21 c22 = a21 a22
b21 b22 b21 b22
[︃ ]︃ [︃ ]︃ [︃ ]︃ [︃ ]︃
[︁ ]︁ b11 [︁ ]︁ b12 [︁ ]︁ b11 [︁ ]︁ b12
[c11 ]= a11 a12 [c12 ]= a11 a12 [c21 ]= a21 a22 [c22 ]= a21 a22
b21 b22 b21 b22
⎡ ⎤
]︃ b b12
a13 ⎣ 11
[︃ ]︃ [︃
c11 c12 a11 a12
= b21 b22 ⎦
c21 c22 a21 a22 a23
b31 b32
[︃ ]︃ [︃ ]︃ [︃ ]︃ [︃ ]︃ [︃ ]︃
c11 c12 a11 a12 b11 b12 c11 c12 a13 [︁ ]︁
= += b31 b32
c21 c22 a21 a22 b21 b22 c21 c22 a23
[︁ ]︁ [︁ ]︁
c11 [︃c12 = c21 [︃c22 = [︁ ]︁ [︁ ]︁
[︁ c11]︁ [︁ c12 += ]︁ [︁ c21]︁ [︁ c22 += ]︁
]︃ ]︃
[︁ ]︁ b11 b12 [︁ ]︁ b11 b12
a11 a12 a21 a22 a13 b31 b32 a23 b31 b32
b21 b22 b21 b22
[︁ ]︁ [︁ ]︁ [︁ ]︁ [︁ ]︁
c11 = c12 = c21 = c22 = [︁ ]︁ [︁ ]︁ [︁ ]︁ [︁ ]︁
[︁ c11]︁ [︁ += ]︁ [︁ c12]︁ [︁ += ]︁ [︁ c21]︁ [︁ += ]︁ [︁ c22]︁ [︁ += ]︁
[︃ ]︃ [︃ ]︃ [︃ ]︃ [︃ ]︃
[︁ ]︁ b11 [︁ ]︁ b12 [︁ ]︁ b11 [︁ ]︁ b12
a11 a12 a11 a12 a21 a22 a21 a22 a13 b31 a13 b32 a23 b31 a23 b32
b21 b22 b21 b22
[︁ ]︁ [︁ ]︁ [︁ ]︁ [︁ ]︁ [︁ ]︁ [︁ ]︁ [︁ ]︁ [︁ ]︁
[︁ c11
]︁ [︁ = ]︁ [︁ c11]︁ [︁ += ]︁ [︁ c12
]︁ [︁ = ]︁ [︁ c12]︁ [︁ += ]︁ [︁ c21
]︁ [︁ = ]︁ [︁ c21]︁ [︁ += ]︁ [︁ c22
]︁ [︁ = ]︁ [︁ c22]︁ [︁ += ]︁
a11 b11 a12 b21 a11 b12 a12 b22 a21 b11 a22 b21 a21 b12 a22 b22
References
Vectorization
Vector Processing
first first
a operand a0 a1 ··· aL−1 operand
op op
second second
b operand b0 b1 ··· bL−1 operand
XMM15
■ each vector register can be used to hold:
2 16 8-bit bytes
2 8 16-bit integers
2 4 32-bit integers
2 2 64-bit integers
2 4 32-bit single-precision floating-point numbers
2 2 64-bit double-precision floating-point numbers
Code Vectorization
■ -fopenmp
2 enable OpenMP support (which requires GOMP library)
■ -fopenmp-simd
2 enable OpenMP SIMD support (which does not require run-time library)
■ -S
2 produce assembly language output only (instead of object code)
■ -fverbose-asm
2 enable generation of more verbose assembly language output (e.g.,
compiler version and command-line options, source-code lines associated
with assembly instructions, hints on which high-level expressions
correspond to various assembly instruction operands)
■ suppose that:
constexpr int n = 5;
int a_data[n] = {-1, -2, -3, -4, -5};
int b_data[n] = {0, 1, 2, 3, 4};
int* a = a_data;
int* b = b_data;
■ sequential loop:
for (int i = 1; i < n; ++i) {
a[i] = a[i - 1] + b[i]
}
■ computation for loop iteration:
i a[i - 1] b[i] a[i]
1 -1 1 0
2 0 2 2
3 2 3 5
4 5 4 9
■ upon loop termination, array pointed to by a contains:
{-1, 0, 2, 5, 9}
p q
■ aliasing often limits ability of compiler to perform optimization
■ in effect, aliasing can introduce new data dependencies that would not
otherwise exist
■ failing to take aliasing into account could lead to illegal optimizations (i.e.,
optimizations that change code behavior)
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 2253
Aliasing and Optimization: An Example
■ consider code:
1 void func(int* a, int* b, int* c) {
2 *a = 42;
3 *b = 0;
4 *c = *a;
5 }
■ at first glance, might seem that code can be optimized to yield:
1 void func(int* a, int* b, int* c) {
2 *a = 42;
3 *b = 0;
4 *c = 42;
5 }
■ above optimized code is incorrect, since a might equal b, in which case *c
should be assigned 0, not 42
■ for reasons of performance, vector load and store operations often impose
restrictions on data alignment
■ typically, target address for vector load or store of n-byte register needs to
be aligned on n-byte boundary
■ for some architectures, such alignment is strict requirement (i.e., code will
not work if data misaligned)
■ for other architectures, such alignment is not strictly required, but
substantial performance penalty may be incurred in case of misaligned
data
■ for this reason, important to align data appropriately whenever possible
■ also, to allow compiler to vectorize in most effective manner possible,
important to let compiler know when data is appropriately aligned
■ vectorization can often provide significant speedup (in some cases linear
with vectorization factor), but costs need to be considered
■ vector loop bodies can be larger than their scalar forms, as more complex
operations may be needed, increasing code size
■ vector loop may have increased startup costs to prepare for vectorized
execution
■ if aliasing is potential problem, require overhead of runtime aliasing check
■ vector instructions may take more cycles
■ source code:
1 #include <cstddef>
2
3 template <std::size_t n, class T>
4 void add(const T (&a)[n], T (&b)[n]) {
5 for (int i = 0; i < n; ++i) {
6 b[i] += a[i];
7 }
8 }
■ since a and b may be aliased, compiler must generate code that correctly
handles aliased case (as well as non-aliased case)
■ often, will generate code that tests for aliasing at run time and uses result
to decide between code for aliased case or non aliased case
■ since compiler does not know alignment of a and b, must generate code
that handles any valid alignment
■ source code:
1 #include <cstddef>
2
3 template <std::size_t n, class T>
4 void add(const T (&__restrict__ a)[n],
5 T (&__restrict__ b)[n]) {
6 for (int i = 0; i < n; ++i) {
7 b[i] += a[i];
8 }
9 }
■ compiler can assume no aliasing (due to use of __restrict__)
■ since compiler does not know alignment of a and b, must generate code
that handles any valid alignment
■ linear(list[:linear-step])
2 specifies that, for every iteration of original scalar loop, each variable in list
is incremented by particular step step (i.e., variable is incremented by step
times vector length for vectorized loop)
■ private(list)
2 declares variables in list to be private to each iteration
■ lastprivate(list)
2 declares variables in list to be private to each iteration, and last value is
copied out from last iteration instance
■ reduction(operator:list)
2 specifies variables in list are reduction variables for operator operator
1 #include <cstddef>
2 #include <iostream>
3 #include <numeric>
4
5 #pragma omp declare simd notinbranch
6 float func(float a, float b) {
7 return a * a + b * b;
8 }
9
10 int main() {
11 constexpr std::size_t n = 65536;
12 constexpr std::size_t align = 16 * alignof(float);
13 alignas(align) static float a[n];
14 alignas(align) static float b[n];
15 alignas(align) static float c[n];
16 std::iota(a, &a[n], 0);
17 std::iota(b, &b[n], 0);
18 #pragma omp simd aligned(a, b, c : align)
19 for (int i = 0; i < n; ++i) {
20 c[i] = func(a[i], b[i]);
21 }
22 for (auto x : c) {
23 std::cout << x << ’\n’;
24 }
25 }
References
■ command-line interface:
sort [-r] [-k $n] [-n]
■ supported command-line options:
Option Description
-k $n Sort using nth field in record; if not specified, n
defaults to 1.
-n Treat key as real number (instead of string) for
sorting purposes; if not specified, key treated as
string.
-r Sort in descending (instead of ascending) order; if
not specified, defaults to ascending order.
■ give examples illustrating expected use cases
■ Key alias for type that represents sort key (alias for std::string)
■ Compare functor class for comparing Key objects
■ Dataset class represents collection of all records
■ specify all class interfaces (i.e., public members)
■ Dataset class provides:
2 constructor that creates dataset by reading all records from input stream
2 function to output all records in sorted order to output stream
■ Dataset class to use std::multimap<Key, std::string, Compare>
■ allows n records to be sorted in O(n log n) time [n insertions, each
requiring O(log n) time]
■ handling n records requires O(n) memory
■ only uses C++ standard library
Software Testing
■ Clang
. . . . . . . .Tidy
.....
■ Clang Static Analyzer
. . . . . . . . .................
■ CppCheck (http://cppcheck.sourceforge.net)
■ Cpplint (http://github.com/cpplint/cpplint)
■ many commercial products also available, such as:
2 Coverity Scan (http://scan.coverity.com), which is free for use in
open-source projects
2 CppDepend (http://www.cppdepend.com)
2 Klocwork
(http://www.roguewave.com/products-services/klocwork)
2 PVS-Studio Analyzer (http://www.viva64.com/en/pvs-studio)
■ advantages:
2 equivalence classes allow many inputs to be tested using one
representative element from equivalence class, greatly reducing number of
test cases
2 since equivalence classes are disjoint, can eliminate/reduce redundancy in
tests
■ disadvantages:
2 just because members of equivalence class should in theory behave
similarly does not means that they actually will in practice (e.g., due to
unanticipated bugs)
3 no overflow:
2 x ≥ 0 and y < 0; or x < 0 and y ≥ 0; or
2 x ≥ 0 and y ≥ 0 and y ≤ INT_MAX − x; or
2 x < 0 and y < 0 and y ≥ INT_MIN − x
y
INT_MAX
x
INT_MIN INT_MAX
INT_MIN
■ could then use three test cases, one from each equivalence class
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 2309
Boundary-Value Testing
x
INT_MIN INT_MAX
INT_MIN
■ example: some test cases for binary search in container (e.g., array)
might include:
2 exactly one element in container being searched
2 container size is power of 2
2 container size is one greater and one less than power of 2
2 query element in container
2 query element not in container
■ example: to test algorithm that calculates sinc function, might use
knowledge that handling cases of computing sinc(x) = sin(x)/x for x = 0
and x ̸= 0 likely to be performed differently, leading to at least two test
cases:
2 test for both zero and nonzero values
declarations:
int n; double x; double y;
1 1 switch (n) {
2 case 0:
3 y = 0.0;
4 break;
3 6 9 5 case 1:
6 y = 2.0 * x;
7 break;
8 case 2:
12 9 y = 0.5 * x * x;
10 break;
11 }
12 // ...
1
declarations:
int n;
2 1 while (n > 0) {
2 --n;
3 }
4 // ...
4
2
declarations:
int n;
3 1 do {
2 --n;
3 while (n > 0);
4 // ...
4
1a
1b
declarations:
int a[1024];
2 1 for (int i = 0; i < 1024; ++i) {
2 a[i] = 0;
3 }
4 // ...
1c
Condition/ Multiple
Statement Decision Condition Decision MCDC Condition
Coverage Criterion Coverage Coverage Coverage Coverage Coverage Coverage
every point of entry and exit invoked at ✓ ✓ ✓ ✓ ✓
least once
every statement executed at least once ✓ ✓∗ ✓∗ ✓∗ ✓∗
every decision has taken all possible out- ✓ ✓ ✓ ✓
comes at least once
every condition in each decision has ✓ ✓ ✓ ✓
taken all possible outcomes at least once
every condition in each decision shown ✓ ✓
to independently affect that decision’s out-
come
every combination of condition outcomes ✓
within each decision invoked at least once
∗ not explicitly required in coverage definition but always implicitly satisfied
initialize x, y; if (y >= 0)
All Paths
Statement
Function
■ performance testing checks how software will behave and perform under
various workloads
■ may consider such factors as:
2 speed
2 resource usage (e.g., memory, disk, and network)
2 reliability
2 scalability
2 responsiveness
2 throughput
■ load testing checks how software behaves under anticipated workloads
■ stress testing checks how software behaves under extreme workloads
■ soak testing (also known as endurance testing) checks how well
software can handle expected workload over long periods of time
■ Catch2
........
■ Google Test (http://github.com/google/googletest)
References
1 Marshall Clow. Making Your Library More Reliable with Fuzzing. C++Now,
Aspen, CO, USA, May 10, 2018. Available online at
https://youtu.be/LlLJRHToyUk.
2 Craig Young. Fuzz Smarter, Not Harder: An AFL Fuzz Primer. BSidesSF,
San Francisco, CA, USA, Feb. 28–29, 2016. Available online at
https://youtu.be/29RbO5bftwo.
3 Daniel Marjamaki. Cppcheck: Static Analysis of C++ Code.
SwedenCpp::Stockholm, Sundbyberg, Sweden, Dec. 11, 2018. Available
online at https://youtu.be/ATkTqBqjYBY.
Mathematics
Set Theory
■ A relation < is said to be a strict total order on a set S if the relation is:
2 asymmetric (i.e., for all a, b ∈ S, if a < b then not (b < a));
2 irreflexive (i.e., for all a ∈ S, not (a < a)); and
2 semiconnex (i.e., for all a, b ∈ S, a < b or b < a or a = b).
■ Informally, each element in the set has a rank (from least to greatest) and
this rank is unique.
■ Connexity/semiconnexity ensures that every two elements in the set are
comparable.
■ Given a, b ∈ S, exactly one of the following must be true:
1 a is less than b
2 a is greater than b
3 a is equal to b
■ Each non-strict total order has an associated strict total order and vice
versa.
■ A non-strict total order ≤ on the set S has the associated strict total
order < defined by:
for all a, b ∈ S, a < b if not b ≤ a.
■ A strict total order < on S has the associated non-strict total order ≤
defined by:
for all a, b ∈ S, a ≤ b if not b < a.
■ Furthermore, each corresponding pair of non-strict and strict total orders
≤ and < on the set S has the associated corresponding complements >
and ≥ given by:
for a, b ∈ S, a ≥ b if not a < b and
for a, b ∈ S, a > b if not a ≤ b.
■ So, any one of the strict/non-strict total orders ≤, ≥, <, and >, completely
determines the others.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 2379
Total Preorders
2 a is greater than b
■ A relation on the set of integer vectors that compares the vectors by their
length is a total preorder.
2 a is greater than b
Boolean Algebra
associativity of conjunction (x ∧ y) ∧ z = x ∧ (y ∧ z)
associativity of disjunction (x ∨ y) ∨ z = x ∨ (y ∨ z)
commutativity of conjunction x∧y = y∧x
commutativity of disjunction x∨y = y∨x
distributivity of conjunction over disjunction x ∧ (y ∨ z) = (x ∧ y) ∨ (x ∧ z)
distributivity of disjunction over conjunction x ∨ (y ∧ z) = (x ∨ y) ∧ (x ∨ z)
identity of disjunction x∨0 = x
identity of conjunction x∧1 = x
annihilator for conjunction x∧0 = 0
annihilator for disjunction x∨1 = 1
idempotence of disjunction x∨x = x
idempotence of conjunction x∧x = x
absorption 1 x ∧ (x ∨ y) = x
absorption 2 x ∨ (x ∧ y) = x
complementation 1 x ∧ ¬x = 0
complementation 2 x ∨ ¬x = 1
double negation ¬¬x = x
De Morgan 1 ¬x ∧ ¬y = ¬(x ∨ y)
De Morgan 2 ¬x ∨ ¬y = ¬(x ∧ y)
conjunctions
■ as alternative to computing by hand, Wolfram Alpha can be used to find
CNF: https://www.wolframalpha.com/input/?i=CNF+x
■ another key form for boolean expression known as disjunctive normal form
■ boolean expression written in manner that meets following requirements
said to be in disjunctive normal form (DNF):
2 each clause allowed to contain only conjunction and negation operations
2 if more than one clause, clauses joined by disjunctions
disjunctions
■ as alternative to computing by hand, Wolfram Alpha can be used to find
DNF: https://www.wolframalpha.com/input/?i=DNF+x
Debuggers
help
Print help information.
quit
Exit debugger.
run [arglist]
Start the program (with arglist if specified).
print expr
Display the value of the expression expr.
bt
Display a stack backtrace.
list
Type the source code lines in the vicinity of where the program is currently
stopped.
break function
Set a breakpoint at the function function.
watch expr
Set a watchpoint for the expression expr.
c
Continue running the program (e.g., after stopping at a breakpoint).
next
Execute the next program line, stepping over any function calls in the line.
step
Execute the next program line, stepping into any function calls in the line.
Code Sanitizers
2 allocator_may_return_null
2 check_initialization_order
2 detect_stack_use_after_return
2 new_delete_type_mismatch
2 exitcode
global_buffer_overflow.cpp
1 #include <iostream>
2 int a[4] = {1, 2, 3, 4};
3 int main() {
4 for (int i = 0; i <= 4; ++i) {
5 std::cout << a[i] << ’\n’;
6 }
7 }
use_after_scope.cpp
1 #include <iostream>
2 int main() {
3 int* p;
4 {int x = 0; p = &x;}
5 std::cout << *p << ’\n’;
6 }
memory_leak.cpp
1 #include <iostream>
2 #include <cstring>
3 int main() {
4 char* p = new char[1024];
5 std::strcpy(p, "Hello, World!\n");
6 std::cout << p;
7 }
2 verbosity
2 report_bugs
2 history_size
2 suppressions
2 exitcode
uninitialized_1.cpp
1 int main(int argc, char** argv) {
2 int x[2];
3 x[0] = 1;
4 if (x[argc % 2]) {
5 return 1;
6 }
7 }
2 strip_path_prefix
2 verbosity
signed_integer_overflow.cpp
1 #include <iostream>
2 #include <limits>
3 int main() {
4 int x = std::numeric_limits<int>::max();
5 int y = x + 1;
6 std::cout << y << ’\n’;
7 }
program output:
signed_integer_overflow.cpp:5:14: runtime error: signed integer
overflow: 2147483647 + 1 cannot be represented in type ’int’
-2147483648
invalid_shift.cpp
1 #include <iostream>
2 int main() {
3 int x = 32678;
4 int y = 1 << x;
5 std::cout << y << ’\n’;
6 }
program output:
invalid_shift.cpp:4:12: runtime error: shift exponent 32678 is too
large for 32-bit type ’int’
0
2 verbosity
heap_buffer_overflow.cpp
1 #include <iostream>
2 #include <cstring>
3 int main() {
4 char* p = new char[1024];
5 std::strcpy(p, "Hello, World!\n");
6 std::cout << p;
7 }
program output:
Hello, World!
=================================================================
==10786==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 1024 byte(s) in 1 object(s) allocated from:
#0 0x7faa5e0a7436 in operator new[](unsigned long) ../../../../src/
libsanitizer/lsan/lsan_interceptors.cc:164
#1 0x400894 in main memory_leak.cpp:4
SUMMARY: LeakSanitizer: 1024 byte(s) leaked in 1 allocation(s).
References
■ can be used with most build processes (e.g., CMake, Make, or direct
compiler invocation)
■ incurs cost of processing code with static analyzer in addition to
compilation
■ static analysis can be much slower than compilation since detailed
analysis of code can incur significant computational cost
■ static analyzer can sometimes yield false positives
■ web site: https://clang-analyzer.llvm.org
■ uninitialized arguments
■ dereferencing null pointers
■ division by zero
■ address of stack memory that escapes function
■ undefined result of binary operator
■ uninitialized array subscript
■ assigning uninitialized values
■ uninitialized branch condition
■ blocks that capture uninitialized values
■ uninitialized value being returned from function
■ new-delete mismatch
■ must run CMake configure operation and build operation with scan-build
■ all should work fine as long as CMAKE_CXX_COMPILER not set by
CMakeLists file or on command line
■ for example:
scan-build cmake -S$SOURCE_DIR -B$BINARY_DIR
scan-build cmake --build $BINARY_DIR
1 #include <iostream>
2
3 void func(int i) {
4 for (int j; j < i; ++j) {
5 std::cout << "hello\n";
6 }
7 }
1 void func() {
2 int *ip = new int[1024];
3 // ...
4 delete ip;
5 }
References
1 Gabor Horvath. Make Friends with the Clang Static Analysis Tools.
CppCon, Bellevue, WA, USA, Sept. 18–23, 2016. Available online at
https://youtu.be/AQF6hjLKsnM.
Clang-Tidy
divide_by_zero.cpp
1 int func(int x) {
2 if (!x) {
3 return 1024 / x;
4 } else {
5 return x;
6 }
7 }
clang-tidy output:
divide_by_zero.cpp:3:15: warning: Division by zero [clang-analyzer-core
.DivideZero]
return 1024 / x;
^
divide_by_zero.cpp:2:6: note: Assuming ’x’ is 0
if (!x) {
^
divide_by_zero.cpp:2:2: note: Taking true branch
if (!x) {
^
divide_by_zero.cpp:3:15: note: Division by zero
return 1024 / x;
^
clang-tidy output:
new_delete_mismatch.cpp:3:2: warning: ’delete’ applied to a pointer that was
allocated with ’new[]’; did you mean ’delete[]’? [clang-diagnostic-
mismatched-new-delete]
delete p;
^
[]
new_delete_mismatch.cpp:2:12: note: allocated with ’new[]’ here
char* p = new char[1024];
^
new_delete_mismatch.cpp:3:2: warning: Memory allocated by ’new[]’ should be
deallocated by ’delete[]’, not ’delete’ [clang-analyzer-unix.
MismatchedDeallocator]
delete p;
^
new_delete_mismatch.cpp:2:12: note: Memory is allocated
char* p = new char[1024];
^
new_delete_mismatch.cpp:3:2: note: Memory allocated by ’new[]’ should be
deallocated by ’delete[]’, not ’delete’
delete p;
^
no_return.cpp
1 int func(int x) {
2 if (x >= 0) {
3 return 1;
4 }
5 }
clang-tidy output:
no_return.cpp:5:1: warning: control may reach end of non-void function
[clang-diagnostic-return-type]
}
^
stack_address_escape.cpp
1 int* p;
2
3 void test() {
4 int x = 42;
5 p = &x;
6 }
clang-tidy output:
stack_address_escape.cpp:6:1: warning: Address of stack memory
associated with local variable ’x’ is still referred to by the
global variable ’p’ upon returning to the caller. This will be a
dangling reference [clang-analyzer-core.StackAddressEscape]
}
^
stack_address_escape.cpp:6:1: note: Address of stack memory associated
with local variable ’x’ is still referred to by the global variable
’p’ upon returning to the caller. This will be a dangling
reference
}
^
undefined_operand.cpp
1 int test() {
2 int x;
3 return x + 1;
4 }
clang-tidy output:
undefined_operand.cpp:3:11: warning: The left operand of ’+’ is a
garbage value [clang-analyzer-core.UndefinedBinaryOperatorResult]
return x + 1;
^
undefined_operand.cpp:2:2: note: ’x’ declared without an initial value
int x;
^
undefined_operand.cpp:3:11: note: The left operand of ’+’ is a garbage
value
return x + 1;
^
2 app.cpp
3 lib.cpp
CMakeLists.txt
1 cmake_minimum_required(VERSION 3.6 FATAL_ERROR)
2 project(clang_tidy LANGUAGES CXX)
3
4 find_program(CLANG_TIDY_PROGRAM NAMES "clang-tidy"
5 DOC "Path to clang-tidy executable")
6 if (CLANG_TIDY_PROGRAM)
7 set(CLANG_TIDY_OPTIONS "-warnings-as-errors=*")
8 set(RUN_CLANG_TIDY "${CLANG_TIDY_PROGRAM}" "${CLANG_TIDY_OPTIONS}")
9 endif()
10
11 add_library(lib lib.cpp)
12 add_executable(app app.cpp lib)
13 set(targets lib app)
14
15 if (CLANG_TIDY_PROGRAM)
16 set_target_properties(${targets} PROPERTIES CXX_CLANG_TIDY
17 "${RUN_CLANG_TIDY}")
18 endif()
lib.cpp
1 int func() {}
2
3 char* foobar() {
4 char c;
5 return &c;
6 }
app.cpp
1 int func();
2 char* foobar();
3
4 int main() {
5 auto x = 1 / 2;
6 double y = 1 / x;
7 char* cp = new char[1024];
8 delete cp;
9 func();
10 foobar();
11 int i;
12 return i + 1;
13 }
References
1 Daniel Jasper. Keep Your Code Sane With Clang Tidy. Meeting C++,
Berlin, Germany, Dec. 4–5, 2015. Available online at
https://youtu.be/nzCLcfH3pb0.
Valgrind
data_race_1_0.cpp
1 #include <thread>
2 #include <iostream>
3
4 unsigned long count = 0;
5
6 void func() {
7 for (unsigned long i = 0; i < 1’000’000; ++i) {
8 ++count;
9 }
10 }
11
12 int main() {
13 std::thread t1(func);
14 std::thread t2(func);
15 t1.join();
16 t2.join();
17 std::cout << count << ’\n’;
18 }
memory_leak.cpp
1 int main() {
2 char* buf = new char[1024];
3 double* dp = new double;
4 // ... (no delete or delete[])
5 }
new_delete_mismatch.cpp
1 int main() {
2 char* buf = new char[1024];
3 // ...
4 delete buf;
5 }
uninitialized_memory.cpp
1 #include <iostream>
2
3 int main() {
4 int i;
5 std::cout << i << ’\n’;
6 }
command:
valgrind --tool=memcheck -q --leak-check=yes ./memory_leak
output:
==15265== 8 bytes in 1 blocks are definitely lost in loss record 1 of 2
==15265== at 0x4C2E1FC: operator new(unsigned long) (vg_replace_malloc.c:334)
==15265== by 0x400611: main (memory_leak.cpp:3)
==15265==
==15265== 1,024 bytes in 1 blocks are definitely lost in loss record 2 of 2
==15265== at 0x4C2E8E9: operator new[](unsigned long) (vg_replace_malloc.c:423)
==15265== by 0x400603: main (memory_leak.cpp:2)
==15265==
command:
valgrind -q --tool=memcheck ./new_delete_mismatch
output:
==15267== Mismatched free() / delete / delete []
==15267== at 0x4C2F21A: operator delete(void*) (vg_replace_malloc.c:576)
==15267== by 0x400638: main (new_delete_mismatch.cpp:4)
==15267== Address 0x5ab4c80 is 0 bytes inside a block of size 1,024 alloc’d
==15267== at 0x4C2E8E9: operator new[](unsigned long) (vg_replace_malloc.c:423)
==15267== by 0x400623: main (new_delete_mismatch.cpp:2)
==15267==
output (truncated):
==15266== Conditional jump or move depends on uninitialised value(s)
==15266== at 0x4F3B8CB: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::
ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<
char, std::char_traits<char> >, std::ios_base&, char, long) const (locale_facets.tcc:874)
==15266== by 0x4F47234: put (locale_facets.h:2371)
==15266== by 0x4F47234: std::ostream& std::ostream::_M_insert<long>(long) (ostream.tcc:73)
==15266== by 0x400798: main (uninitialized_memory.cpp:5)
==15266==
==15266== Use of uninitialised value of size 8
==15266== at 0x4F3B3DE: int std::__int_to_char<char, unsigned long>(char*, unsigned long, char const*, std
::_Ios_Fmtflags, bool) (locale_facets.tcc:803)
==15266== by 0x4F3B8F4: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::
ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<
char, std::char_traits<char> >, std::ios_base&, char, long) const (locale_facets.tcc:876)
==15266== by 0x4F47234: put (locale_facets.h:2371)
==15266== by 0x4F47234: std::ostream& std::ostream::_M_insert<long>(long) (ostream.tcc:73)
==15266== by 0x400798: main (uninitialized_memory.cpp:5)
==15266==
==15266== Conditional jump or move depends on uninitialised value(s)
==15266== at 0x4F3B3EB: int std::__int_to_char<char, unsigned long>(char*, unsigned long, char const*, std
::_Ios_Fmtflags, bool) (locale_facets.tcc:806)
==15266== by 0x4F3B8F4: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::
ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<
char, std::char_traits<char> >, std::ios_base&, char, long) const (locale_facets.tcc:876)
==15266== by 0x4F47234: put (locale_facets.h:2371)
==15266== by 0x4F47234: std::ostream& std::ostream::_M_insert<long>(long) (ostream.tcc:73)
==15266== by 0x400798: main (uninitialized_memory.cpp:5)
==15266==
command:
ms_print --x=70 --y=10 profile.massif
output (truncated):
--------------------------------------------------------------------------------
Command : ./ heap_1
Massif arguments : -- massif -out - file = profile . massif -- stacks = yes
ms_print arguments : --x =70 --y =10 profile . massif
--------------------------------------------------------------------------------
MB
2.071^ ##
| # :::::::::::::::::::::: @ :::::: @ :::
| # :: : ::::::::::::::::: @ :::::: @ :::
| # :: : ::::::::::::::::: @ :::::: @ :::
| # :: : ::::::::::::::::: @ :::::: @ :::
| @ ::: @ :::: @ :::::::# :: : ::::::::::::::::: @ :::::: @ :::
| @: :@ :::: @ ::: :: # :: : ::::::::::::::::: @ :::::: @ :::
| @: :@ :::: @ ::: :: # :: : ::::::::::::::::: @ :::::: @ :::
| @ :::::: @:@: :@ :::: @ ::: :: # :: : ::::::::::::::::: @ :::::: @ :::
| :::: @ :::: :@:@: :@ :::: @ ::: :: # :: : ::::::::::::::::: @ :::::: @ :::
0 +---------------------------------------------------------------------> Mi
0 312.7
Number of snapshots : 71
Detailed snapshots : [8 , 14 , 16 , 19 , 24 , 31 ( peak ) , 55 , 65]
--------------------------------------------------------------------------------
n time (i) total (B) useful - heap (B) extra - heap (B) stacks (B)
--------------------------------------------------------------------------------
0 0 0 0 0 0
1 5 ,653 ,795 90 ,704 90 ,112 24 568
2 9 ,989 ,271 139 ,760 139 ,264 24 472
3 15 ,104 ,837 205 ,296 204 ,800 24 472
4 19 ,380 ,693 205 ,296 204 ,800 24 472
5 25 ,946 ,699 336 ,296 335 ,872 24 400
6 30 ,435 ,811 336 ,296 335 ,872 24 400
References
■ build program app using GCC, ensuring that --coverage option is used
for both compiling and linking; for example, use command sequence like:
g++ -O0 --coverage -c app.cpp
g++ -O0 --coverage -c signum.cpp
g++ -O0 --coverage -o app app.o signum.o
■ run app program twice as follows:
./app 0
./app 1
■ since app program run twice, statistics are accumulated from both runs of
program
■ run Gcov; for example, use command like:
gcov -b -c -m app.o signum.o
■ view resulting .gcov files (examples of which are given on following slides)
■ LLVM Cov is code coverage analysis tool, which is part of LLVM software,
intended for use with Clang compiler
■ supports measurement of function, statement, and decision (i.e., branch)
coverage
■ manner in which coverage information collected similar to case of Gcov
■ LLVM Cov provided as program called llvm-cov
■ coverage data files produced by compiler and program execution
processed with gcov subcommand of llvm-cov
■ gcov subcommand of llvm-cov has very similar interface as gcov
program (of Gcov)
■ web site: http://llvm.org/docs/CommandGuide/llvm-cov.html
■ build program app using Clang, ensuring that --coverage option is used
for both compiling and linking; for example, use command sequence like:
clang++ -O0 --coverage -c app.cpp
clang++ -O0 --coverage -c signum.cpp
clang++ -O0 --coverage -o app app.o signum.o
■ run app program twice as follows:
./app 0
./app 1
■ since app program run twice, statistics are accumulated from both runs of
program
■ run LLVM Cov; for example, use command like:
llvm-cov gcov -b -c app.o signum.o
■ view resulting .gcov files (which look similar to ones produced by Gcov;
see earlier Gcov example)
■ coverage report can be generated in HTML format from one or more trace
files with genhtml program, which has following command-line interface:
genhtml [options] $trace_file...
■ some commonly-used options include:
Option Description
--legend include legend in report
--branch-coverage include branch coverage information in re-
port
-o dir set output directory for HTML document to
dir
-h print help information and exit
utility.hpp
1 class Flag {
2 public:
3 explicit Flag(bool b = false) noexcept : b_(b) {}
4 explicit operator bool() const noexcept {return b_;}
5 Flag operator&&(const Flag& other) noexcept {
6 return Flag(b_ && other.b_);
7 }
8 Flag operator||(const Flag& other) noexcept {
9 return Flag(b_ || other.b_);
10 }
11 private:
12 bool b_;
13 };
main.cpp
1 #include <tuple>
2 #include "utility.hpp"
3
4 bool func(bool a, bool b, bool c) noexcept {
5 return (a || b) && c;
6 }
7
8 Flag func(Flag a, Flag b, Flag c) noexcept {
9 return (a || b) && c;
10 }
11
12 int main() {
13 std::tuple<bool, bool, bool> tests[] = {{0, 0, 0}, {1, 1, 1}};
14 for (auto&& [a, b, c] : tests) {
15 func(a, b, c);
16 func(Flag(a), Flag(b), Flag(c));
17 }
18 }
example_1_1.cpp
1 struct Gadget {
2 Gadget();
3 virtual ~Gadget();
4 };
5 Gadget::Gadget() {}
6 Gadget::~Gadget() {}
7
8 struct Widget : Gadget {
9 Widget();
10 virtual ~Widget();
11 };
12 Widget::Widget() {}
13 Widget::~Widget() {}
14
15 int main() {
16 Widget w;
17 }
example_1_2.cpp
1 struct Gadget {
2 Gadget();
3 virtual ~Gadget();
4 };
5 Gadget::Gadget() {}
6 Gadget::~Gadget() {}
7
8 struct Widget : Gadget {
9 Widget();
10 virtual ~Widget();
11 };
12 Widget::Widget() {}
13 Widget::~Widget() {}
14
15 int main() {
16 Widget w;
17 Widget* wp = new Widget;
18 delete wp;
19 // invoke deleting destructor (Itanium C++ ABI)
20 Gadget* gp = new Gadget;
21 delete gp;
22 // invoke deleting destructor (Itanium C++ ABI)
23 }
CodeCoverage.cmake
■ slightly modified version of above module can be found at:
2 https://github.com/mdadams/cmake-modules/blob/master/
CodeCoverage.cmake
CMakeLists.txt
1 cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
2 project(coverage_example LANGUAGES CXX C)
3 option(ENABLE_COVERAGE "Enable coverage" false)
4 set(CMAKE_VERBOSE_MAKEFILE true)
5 set(CMAKE_EXPORT_COMPILE_COMMANDS true)
6
7 if (ENABLE_COVERAGE)
8 set(CMAKE_BUILD_TYPE "Debug" CACHE STRING
9 "Set the build type." FORCE)
10 include(CodeCoverage.cmake)
11 append_coverage_compiler_flags()
12 endif()
13
14 add_executable(app app.cpp)
15
16 if (ENABLE_COVERAGE)
17 setup_target_for_coverage_lcov(
18 NAME coverage
19 EXECUTABLE ${CMAKE_CURRENT_SOURCE_DIR}/run_tests
20 EXECUTABLE_ARGS ${CMAKE_CURRENT_BINARY_DIR}
21 LCOV_ARGS -rc lcov_branch_coverage=1
22 GENHTML_ARGS --branch-coverage
23 DEPENDENCIES app)
24 endif()
app.cpp
1 #include <iostream>
2
3 void func(int x) {
4 if (x % 2) {
5 std::cout << "odd\n";
6 } else {
7 std::cout << "even\n";
8 }
9 }
10
11 int main() {
12 for (int i = 0; i < 100; ++i) {
13 func(i);
14 }
15 }
run_tests
1 #! /usr/bin/env bash
2
3 BINARY_DIR="$1"
4
5 $BINARY_DIR/app
References
LLVM XRay
2 performs basic function call accounting statistics with various options for
sorting and output formats
2 convert
2 converts XRay log file from one format to another
2 extract
2 extracts instrumentation map from binary and returns as YAML
2 graph
2 generates DOT graph of function call relationships between functions in XRay
trace
2 stack
2 reconstructs function call stacks from timeline of function calls in XRay trace
command:
llvm-xray account -k -m ./example_1 xray-log.example_1.5fNAQv
output produced:
Functions with latencies: 3
funcid count [ min, med, 90p, 99p, max] sum function
1 1000 [ 0.000782, 0.000827, 0.000835, 0.000899, 0.000919] 0.825670 <invalid>:0:0: delay()
2 1 [ 0.420710, 0.420710, 0.420710, 0.420710, 0.420710] 0.420710 <invalid>:0:0: func(int,
unsigned long)
3 1000 [ 0.000781, 0.000825, 0.000831, 0.000896, 0.000917] 0.822853 <invalid>:0:0: void std::
this_thread::sleep_for<long, std::ratio<1l, 1000l> >(std::chrono::duration<long, std::ratio<1
l, 1000l> > const&)
output produced:
Unique Stacks: 2
Top 10 Stacks by leaf sum:
Sum: 1439155104
lvl function count sum
#0 func(int, unsigned long) 1 1475472614
#1 delay() 500 1446943616
#2 void std::this_thread::sleep_for<long, std::ratio<1l, 100... 499 1439155104
Sum: 1437712794
lvl function count sum
#0 func(int, unsigned long) 1 1472486054
#1 delay() 500 1442899912
#2 void std::this_thread::sleep_for<long, std::ratio<1l, 100... 500 1437712794
command:
llvm-xray convert -f yaml -y -m ./example_1 xray-log.example_1.5fNAQv
output produced:
---
header:
version: 3
type: 0
constant-tsc: true
nonstop-tsc: true
cycle-frequency: 3500000000
records:
- { type: 0, func-id: 2, function: ’func(int, unsigned long)’, args: [ 0 ], cpu: 5, thread: 29992,
process: 29991, kind: function-enter-arg, tsc: 1027021087397834 }
- { type: 0, func-id: 2, function: ’func(int, unsigned long)’, args: [ 1 ], cpu: 4, thread: 29993,
process: 29991, kind: function-enter-arg, tsc: 1027021087460270 }
- { type: 0, func-id: 1, function: ’delay()’, cpu: 5, thread: 29992, process: 29991, kind: function-enter
, tsc: 1027021087525002 }
- { type: 0, func-id: 3, function: ’void std::this_thread::sleep_for<long, std::ratio<1l, 1000l> >(std::
chrono::duration<long, std::ratio<1l, 1000l> > const&)’, cpu: 5, thread: 29992, process: 29991,
kind: function-enter, tsc: 1027021087526060 }
- { type: 0, func-id: 1, function: ’delay()’, cpu: 4, thread: 29993, process: 29991, kind: function-enter
, tsc: 1027021087532070 }
- { type: 0, func-id: 3, function: ’void std::this_thread::sleep_for<long, std::ratio<1l, 1000l> >(std::
chrono::duration<long, std::ratio<1l, 1000l> > const&)’, cpu: 4, thread: 29993, process: 29991,
kind: function-enter, tsc: 1027021087533716 }
- { type: 0, func-id: 3, function: ’void std::this_thread::sleep_for<long, std::ratio<1l, 1000l> >(std::
chrono::duration<long, std::ratio<1l, 1000l> > const&)’, cpu: 4, thread: 29993, process: 29991,
kind: function-exit, tsc: 1027021090288388 }
Miscellaneous Tools
Catch2
1 #include <limits>
2 #include <stdexcept>
3
4 class counter {
5 public:
6 using count_type = std::size_t;
7 static constexpr count_type max_count() {
8 return std::numeric_limits<count_type>::max();
9 }
10 counter(count_type count = 0) : count_(count) {}
11 count_type get_count() const {
12 return count_;
13 }
14 void increment() {
15 if (count_ == max_count()) {
16 throw std::overflow_error("counter overflow");
17 }
18 ++count_;
19 }
20 private:
21 count_type count_;
22 };
1 #define CATCH_CONFIG_MAIN
2 #include <catch2/catch.hpp>
3
4 TEST_CASE("addition") {
5 float x = 0.0f;
6 for (int i = 0; i < 10; ++i) {x += 0.1f;}
7 CHECK_NOFAIL(x == 1.0f);
8 // condition may be false due to roundoff error
9 CHECK(x == Approx(1.0f));
10 // should pass
11 CHECK(x == Approx(1.0f).margin(0.01f));
12 // should pass (absolute tolerance 0.01)
13 CHECK(x == Approx(1.0f).epsilon(0.01f));
14 // should pass (relative tolerance 1%)
15 }
1 #include <cstddef>
2 #include <vector>
3
4 // Note: T is not allowed to be bool.
5 template <class T>
6 class Stack {
7 public:
8 bool empty() const {return s_.empty();}
9 std::size_t size() const {return s_.size();}
10 const T& top() const {return s_.back();}
11 void push(const T& x) {s_.push_back(x);}
12 void pop() {s_.pop_back();}
13 private:
14 std::vector<T> s_;
15 };
1 Phil Nash. Modern C++ Testing with Catch2. CppCon, Bellevue, WA,
USA, Sept. 24, 2018. Available online at
https://youtu.be/Ob5_XZrFQH0.
2 Phil Nash. Modern C++ Testing with Catch2. Meeting C++, Berlin,
Germany, Nov. 9, 2017. Available online at
https://youtu.be/3tIE6X5FjDE.
3 Phil Nash. Modern C++ Testing with Catch2. C++ Edinburgh, Edinburgh,
UK, Aug. 14, 2017. Available online at
https://youtu.be/grC0S6ZK59U.
4 Phil Nash. Test Driven C++ with Catch. CppCon, Bellevue, WA, USA,
Sept. 22, 2015. Available online at https://youtu.be/gdzP3pAC6UI.
5 Phil Nash. Testdriven C++ with Catch. Meeting C++, Berlin, Germany,
Dec. 5–6, 2014. Available online at https://youtu.be/C2LcIp56i-8.
Perf
Event Description
cache-misses cache misses
cache-references cache accesses
cycles CPU cycles
cpu-clock CPU wall-time clock
instructions CPU instructions
cs context switches
faults page faults
$ perf list
List of pre - defined events ( to be used in -e ):
branch - instructions OR branches [ Hardware event ]
branch - misses [ Hardware event ]
bus - cycles [ Hardware event ]
cache - misses [ Hardware event ]
cache - references [ Hardware event ]
cpu - cycles OR cycles [ Hardware event ]
instructions [ Hardware event ]
ref - cycles [ Hardware event ]
[ text deleted ]
alignment - faults [ Software event ]
context - switches OR cs [ Software event ]
cpu - clock [ Software event ]
cpu - migrations OR migrations [ Software event ]
[ text deleted ]
L1 - dcache - load - misses [ Hardware cache event ]
L1 - dcache - loads [ Hardware cache event ]
L1 - dcache - prefetch - misses [ Hardware cache event ]
L1 - dcache - store - misses [ Hardware cache event ]
L1 - dcache - stores [ Hardware cache event ]
L1 - icache - load - misses [ Hardware cache event ]
[ text deleted ]
cache - misses OR cpu / cache - misses / [ Kernel PMU event ]
cache - references OR cpu / cache - references / [ Kernel PMU event ]
cpu - cycles OR cpu /cpu - cycles / [ Kernel PMU event ]
instructions OR cpu / instructions / [ Kernel PMU event ]
mem - loads OR cpu /mem - loads / [ Kernel PMU event ]
mem - stores OR cpu /mem - stores / [ Kernel PMU event ]
[ text deleted ]
$ perf stat dd if =/ dev / urandom of =/ dev / null bs =1 K count =32 K status = none
Performance counter stats for
’dd if =/ dev / urandom of =/ dev / null bs =1 K count =32 K status = none ’:
1727.055828 task - clock ( msec ) # 0.999 CPUs utilized
1 context - switches # 0.001 K/ sec
13 cpu - migrations # 0.008 K/ sec
60 page - faults # 0.035 K/ sec
5 ,805 ,261 ,702 cycles # 3.361 GHz
2 ,115 ,865 ,103 stalled - cycles - frontend # 36.45% frontend
cycles idle
<not supported > stalled - cycles - backend
12 ,108 ,757 ,065 instructions # 2.09 insns per cycle
# 0.17 stalled cycles
per insn
254 ,471 ,634 branches # 147.344 M/ sec
257 ,282 branch - misses # 0.10% of all branches
1.728232622 seconds time elapsed
#
# ( For a higher level overview , try : perf report -- sort comm , dso )
#
■ read Perf data (created by Perf record) and display trace output
■ command line interface has following form:
perf script [options]
■ some common options include:
Option Description
-i file set input file to file
--pid pid only show events for process ID pid
--tid tid only show events for thread ID tid
-C cpu only show events for CPU cpu
■ input file defaults to perf.data
■ read Perf data (created by Perf record) and display annotated code
■ command line interface has following form:
perf annotate [options]
■ some common options include:
Option Description
-i file set input file to file
-s sym annotate symbol sym
-d dsos only consider symbols in DSO/object files dsos
-v increase verbosity level
-l print matching source lines
-P do not shorten displayed pathnames
-k file set vmlinux pathname to file
--stdio use stdio interface
--no-source disable displaying of source code
■ input file defaults to perf.data
1 #include <iostream>
2 #include <algorithm>
3
4 constexpr int M = 4096;
5 constexpr int N = 4096;
6
7 [[gnu::noinline]]
8 double naive_sum(const double a[][N]) {
9 double sum = 0.0;
10 for (int j = 0; j < N; ++j) {
11 for (int i = 0; i < M; ++i) {
12 sum += a[i][j];
13 }
14 }
15 return sum;
16 }
17
18 [[gnu::noinline]]
19 double improved_sum(const double a[][N]) {
20 double sum = 0.0;
21 for (int i = 0; i < M; ++i) {
22 for (int j = 0; j < N; ++j) {
23 sum += a[i][j];
24 }
25 }
26 return sum;
27 }
29 int main() {
30 for (int i = 0; i < 16; ++i) {
31 static double a[M][N];
32 static double b[M][N];
33 std::fill_n(&a[0][0], M * N, 1.0 / (M * N));
34 std::fill_n(&b[0][0], M * N, 1.0 / (M * N));
35 std::cout << naive_sum(a) << ’ ’;
36 std::cout << improved_sum(b) << ’\n’;
37 }
38 }
# To display the perf . data header info , please use -- header /-- header - only options .
#
# dso : array_sum
# Samples : 16 K of event ’ cycles :u ’
# Event count ( approx .): 14049539983
#
# Children Self Command Symbol
# ........ ........ ......... .....................
#
99.97% 0.00% array_sum [.] __libc_start_main
|
--- __libc_start_main
0 x46e258d4c544155
99.97% 10.92% array_sum [.] main
|
--- main
__libc_start_main
0 x46e258d4c544155
82.97% 82.97% array_sum [.] naive_sum
|
--- naive_sum
main
__libc_start_main
0 x46e258d4c544155
5.90% 5.90% array_sum [.] improved_sum
|
--- improved_sum
main
__libc_start_main
0 x46e258d4c544155
[ text deleted ]
# To display the perf . data header info , please use -- header /-- header - only options .
#
# dso : array_sum
# Samples : 25 K of event ’cache - misses :u ’
# Event count ( approx .): 256620000
#
# Children Self Command Symbol
# ........ ........ ......... .....................
#
99.99% 0.00% array_sum [.] __libc_start_main
|
--- __libc_start_main
0 x46e258d4c544155
99.99% 3.67% array_sum [.] main
|
--- main
__libc_start_main
0 x46e258d4c544155
93.74% 93.73% array_sum [.] naive_sum
|
--- naive_sum
main
__libc_start_main
0 x46e258d4c544155
2.58% 2.58% array_sum [.] improved_sum
|
--- improved_sum
main
__libc_start_main
0 x46e258d4c544155
[ text deleted ]
■ can generate flamegraphs from Perf data by using software available from
2 https://github.com/brendangregg/FlameGraph
References
1 B. Gregg, The Flame Graph. ACM Queue, March 2016, pages 1–28.
■ often easy to identify in general terms which parts of code are slow
■ sometimes more difficult to pinpoint precise reason why code is slow (i.e.,
what is precise cause of bottleneck)
■ often need to consider factors such as:
2 cache behavior
2 memory and resource contention
2 floating-point efficiency
2 branch behavior
■ often, processor itself in best position to provide information related to
above factors
■ Performance API (PAPI) software provides portable and efficient API for
accessing hardware performance counters found on modern processors
■ more generally allows monitoring of system information on range of
components, such as CPUs, network interface cards, and power monitors
■ consists of library and several utility programs
■ open source
■ written in C
■ supports most mainstream Unix-based operating systems (e.g., Linux, OS
X, and other Unix variants); older versions support Microsoft Windows
■ supports most modern processors (e.g., Intel and AMD 32- and 64-bit
x86, ARM, MIPS, Intel Itanium II, UltraSparc I, II, and III, and IBM Power
4, 5, 6, and 7)
■ web site: http://icl.utk.edu/papi
Function Description
PAPI_accum_counters add current counts to array and reset counters
PAPI_flips get floating-point instruction rate and real and proces-
sor time
PAPI_flops get floating-point operation rate and real and processor
time
PAPI_ipc get instructions per cycle and real and processor time
PAPI_num_counters get number of hardware counters available on system
PAPI_read_counters copy current counts to array and reset counters
PAPI_start_counters start counting hardware events
PAPI_stop_counters stop counters and return current counts
Instruction Mix
Name Description
PAPI_LD_INS number of load instructions
PAPI_SR_INS number of store instructions
PAPI_LST_INS number of load/store instructions
PAPI_BR_INS number of branch instructions
PAPI_INT_INS number of integer instructions
PAPI_FP_INS number of floating-point instructions
PAPI_VEC_INS number of vector/SIMD instructions
PAPI_VEC_SP number of single-precision vector/SIMD instructions
PAPI_VEC_DP number of double-precision vector/SIMD instructions
PAPI_TOT_INS number of instructions in total
Clock Cycles
Name Description
PAPI_TOT_CYC total number of clock cycles
FLOPS
Name Description
PAPI_FP_OPS number of floating-point operations
PAPI_SP_OPS number of floating-point operations executed, optimized to count
scaled single-precision vector operations
PAPI_DP_OPS number of floating-point operations executed, optimized to count
scaled double-precision vector operations
■ most frequently used events are often those related to cache behavior
■ instructions per cycle could be computed from events:
2 PAPI_TOT_CYC and PAPI_TOT_INS
1 #include <iostream>
2 #include <papi.h>
3
4 void do_work() {for (volatile auto i = 1’000’000; i > 0; --i) {}}
5
6 int main() {
7 constexpr int num_events = 2;
8 int events[num_events] = {PAPI_TOT_INS, PAPI_TOT_CYC};
9 long long values[num_events];
10 if (PAPI_start_counters(events, num_events) != PAPI_OK)
11 {std::cerr << "cannot start counters\n"; return 1;}
12 do_work();
13 if (PAPI_stop_counters(values, num_events) != PAPI_OK)
14 {std::cerr << "cannot stop counters\n"; return 1;}
15 for (auto i : values) {std::cout << i << ’\n’;}
16 }
Function Description
PAPI_library_init initialize PAPI library
PAPI_shutdown cleanup PAPI library
PAPI_create_eventset create event set
PAPI_destroy_eventset destroys empty event set
PAPI_cleanup_eventset removes all events from event set
PAPI_add_event add preset or native hardware event to event set
PAPI_add_events add multiple preset or native hardware events to
event set
PAPI_start start counting hardware events in event set
PAPI_read read hardware counters from event set
PAPI_reset reset hardware event counts in event set
PAPI_accum adds hardware counters from event set to elements
in array and resets counters
PAPI_stop stop counting hardware events in event set
Name Description
papi_avail provides availability and detail information for PAPI pre-
set events
papi_clockres measures and reports clock latency and resolution for
PAPI timers
papi_cost computes execution time costs for basic PAPI opera-
tions
papi_command_line executes PAPI preset or native events from command
line
papi_decode provides availability and detail information for PAPI pre-
set events
papi_event_chooser given list of named events, lists other events that can
be counted with them
papi_mem_info provides information on memory architecture of current
processor
papi_native_avail provides detailed information for PAPI native events
papi_version provides version information for PAPI
References
Gprof
Flat profile :
Each sample counts as 0.01 seconds .
% cumulative self self total
time seconds seconds calls s/ call s/ call name
89.48 7.55 7.55 1 7.55 7.55 naive_matmul (
double const (*) [1024] ,
double const (*) [1024] ,
double (*) [1024])
11.23 8.50 0.95 1 0.95 0.95 improved_matmul (
double const (*) [1024] ,
double const (*) [1024] ,
double (*) [1024])
0.12 8.51 0.01 main
0.00 8.51 0.00 1 0.00 0.00
_GLOBAL__sub_I__Z12naive_matmulPA1024_KdS1_PA1024_d
granularity : each sample hit covers 2 byte (s) for 0.12% of 8.51 seconds
index % time self children called name
< spontaneous >
[1] 100.0 0.01 8.50 main [1]
7.55 0.00 1/1naive_matmul ( double const (*) [1024] ,
double const (*) [1024] ,
double (*) [1024]) [2]
0.95 0.00 1/1 improved_matmul ( double const (*) [1024] ,
double const (*) [1024] ,
double (*) [1024]) [3]
-----------------------------------------------
7.55 0.00 1/1 main [1]
[2] 88.7 7.55 0.00 1 naive_matmul ( double const (*) [1024] ,
double const (*) [1024] ,
double (*) [1024]) [2]
-----------------------------------------------
0.95 0.00 1/1 main [1]
[3] 11.1 0.95 0.00 1 improved_matmul ( double const (*) [1024] ,
double const (*) [1024] ,
double (*) [1024]) [3]
-----------------------------------------------
0.00 0.00 1/1 __libc_csu_init [16]
[10] 0.0 0.00 0.00 1
_GLOBAL__sub_I__Z12naive_matmulPA1024_KdS1_PA1024_d [10]
-----------------------------------------------
Index by function name
[10] _GLOBAL__sub_I__Z12naive_matmulPA1024_KdS1_PA1024_d ( matmul_array . cpp ) [3]
improved_matmul ( double const (*) [1024] , double const (*) [1024] , double (*) [1024])
[2] naive_matmul ( double const (*) [1024] , double const (*) [1024] , double (*) [1024])
[1] main
References
Valgrind/Callgrind
■ Callgrind is Valgrind
......... tool that collects function call graph information and
measures number of instructions executed and cache behavior for
program
■ does not measure execution time per se; but provides sufficient
information to make clock cycle estimates (as is done in KCachegrind)
■ can be used to determine cache hit/miss counts and miss rate on program
wide, per function, and per source-code line basis
■ simulates L1 instruction/data cache and L2 cache
■ parameters for each cache can be specified (i.e., size, associativity, and
line size) but default to values taken from machine’s cache
■ simplistic cache model only approximates real cache
■ handles code in shared libraries
■ typically 15 to 100 times slower (depending on whether cache and branch
simulation enabled)
1 #include <iostream>
2 #include <algorithm>
3
4 constexpr int M = 2048;
5 constexpr int N = 2048;
6
7 double naive_sum(const double a[][N]) {
8 double sum = 0.0;
9 for (int j = 0; j < N; ++j) {
10 for (int i = 0; i < M; ++i)
11 {sum += a[i][j];}
12 }
13 return sum;
14 }
15
16 double improved_sum(const double a[][N]) {
17 double sum = 0.0;
18 for (int i = 0; i < M; ++i) {
19 for (int j = 0; j < N; ++j)
20 {sum += a[i][j];}
21 }
22 return sum;
23 }
25 int main() {
26 static double a[M][N];
27 std::fill_n(&a[0][0], M * N, 1.0 / (M * N));
28 std::cout << naive_sum(a) << ’\n’;
29 static double b[M][N];
30 std::fill_n(&b[0][0], M * N, 1.0 / (M * N));
31 std::cout << improved_sum(b) << ’\n’;
32 }
[text deleted]
--------------------------------------------------------------------------------
-- User - annotated source : array_sum . cpp
--------------------------------------------------------------------------------
Ir Dr Dw I1mr D1mr D1mw ILmr DLmr DLmw Bc Bcm Bi Bim
. . . . . . . . . . . . . # include < iostream >
. . . . . . . . . . . . . # include < algorithm >
. . . . . . . . . . . . .
. . . . . . . . . . . . . constexpr int M = 2048;
. . . . . . . . . . . . . constexpr int N = 2048;
. . . . . . . . . . . . .
1 0 0 1 0 0 1 . . . . . . double naive_sum ( const double a [][ N ]) {
4 ,097 . . . . . . . . . . . . double sum = 0.0;
4 ,096 0 0 0 0 0 0 0 0 2 ,048 3 . . for ( int j = 0; j < N; ++ j) {
8 ,390 ,656 0 0 0 0 0 0 0 0 4 ,194 ,304 2 ,063 . . for ( int i = 0; i < M; ++ i)
8 ,388 ,608 4 ,194 ,304 0 0 4 ,194 ,304 0 0 4 ,194 ,304 . . . . . { sum += a[i ][ j ];}
. . . . . . . . . . . . . }
. . . . . . . . . . . . . return sum ;
1 1 0 0 1 0 0 1 . . . . . }
. . . . . . . . . . . . .
1 . . . . . . . . . . . . double improved_sum ( const double a [][ N ]) {
2 ,049 . . . . . . . . . . . . double sum = 0.0;
4 ,096 0 0 0 0 0 0 0 0 2 ,048 3 . . for ( int i = 0; i < M; ++ i) {
8 ,388 ,608 0 0 0 0 0 0 0 0 4 ,194 ,304 2 ,058 . . for ( int j = 0; j < N; ++ j)
8 ,388 ,608 4 ,194 ,304 0 0 524 ,288 0 0 524 ,288 . . . . . { sum += a[i ][ j ];}
. . . . . . . . . . . . . }
. . . . . . . . . . . . . return sum ;
1 1 0 0 1 0 0 1 . . . . . }
. . . . . . . . . . . . .
2 0 0 1 0 0 1 . . . . . . int main () {
. . . . . . . . . . . . . static double a[M ][ N ];
. . . . . . . . . . . . . std :: fill_n (& a [0][0] , M * N , 1.0 / (M * N ));
2 0 1 0 0 1 0 0 1 . . . . std :: cout << naive_sum (a) << ’\n ’;
16 ,787 ,459 4 ,194 ,305 0 1 4 ,194 ,305 0 1 4 ,194 ,305 0 4 ,196 ,352 2 ,066 . . => array_sum . cpp : naive_sum ( double const (*) [2048]) (1 x)
. . . . . . . . . . . . . static double b[M ][ N ];
. . . . . . . . . . . . . std :: fill_n (& b [0][0] , M * N , 1.0 / (M * N ));
2 0 1 0 0 1 0 0 1 . . . . std :: cout << improved_sum (b) << ’\n ’;
16 ,783 ,363 4 ,194 ,305 0 0 524 ,289 0 0 524 ,289 0 4 ,196 ,352 2 ,061 . . => array_sum . cpp : improved_sum ( double const (*) [2048]) (1 x)
6 2 0 1 2 0 1 1 . . . . . }
--------------------------------------------------------------------------------
Ir Dr Dw I1mr D1mr D1mw ILmr DLmr DLmw Bc Bcm Bi Bim
--------------------------------------------------------------------------------
48 92 0 0 100 0 0 100 0 49 14 0 0 percentage of events annotated
References
Build Tools
Build Tools
Make
■ comment starts at hash character (i.e., “#”) and continues until end of line;
example:
# This comment continues until the end of the line.
■ supports variables
■ some important variables used by built-in rules:
Name Description
CXX C++ compiler command
CXXFLAGS C++ compiler options
LDFLAGS linker options
■ to assign value to variable, use equal sign; example:
CXX = g++
■ to substitute value of variable, use dollar sign followed by variable name in
parentheses; example:
$(CXX)
■ normally, each target associated with file of same name (and building
target will create this file)
■ phony target: target that is not associated with any file
■ to identify target as phony make it prerequisite of special target called
“.PHONY”; example (specify all as phony target):
.PHONY: all
■ some special built-in variables that can be used in rules:
Name Description
$@ target
$< name of first prerequisite
$^ names of all of prerequisites separated by spaces
■ all target: builds all of the programs handled by the makefile (e.g.,
hello)
■ clean target: removes all of the executable files and object files produced
by build process (e.g., hello, hello.o)
■ although all and clean have no special meaning to make, very common
practice to provide targets with these particular names in all makefiles
■ hello target: compiles and links the hello program
■ chain of dependencies for all target:
all → hello → hello.o → hello.cpp
■ all and clean examples of phony targets
References
CMake
■ CMake has very large user base and is employed in many open-source
and commercial projects
■ some users of CMake include:
2 Blender (https://www.blender.org)
2 Clang (http://clang.llvm.org)
2 Computational Geometry Algorithms Library (CGAL)
(http://www.cgal.org)
2 JasPer Image Processing/Coding Tool Kit
(http://www.ece.uvic.ca/~mdadams/jasper)
2 K Desktop Environment (KDE) (https://www.kde.org)
2 MySQL (https://www.mysql.com)
2 Netflix (https://www.netflix.com)
2 OpenCV (http://opencv.org)
2 Qt (https://www.qt.io)
2 Second Life (http://secondlife.com)
■ CMake build files: files used by CMake to describe build process for
software project (i.e., CMakeLists.txt and other build files it references)
■ native build tool: program used to build code for particular software
development environment being employed (e.g., make for Unix, MSBuild
for Microsoft Visual Studio, and xcodebuild for Apple Xcode)
■ native build files: files used by native build tool to control build process
(e.g., makefiles for Unix, project/solution files for Microsoft Visual Studio,
and project files for Apple Xcode)
■ build process consists of two steps:
1 CMake used, with CMake build files as input, to produce native build files
2 native build tool invoked to build code using native build files generated by
CMake
■ strictly speaking, CMake does not itself build code, but rather produces
build files that can be used by native build tool to build code
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 2653
CMake Basics
■ source directory: top-level directory of source tree for code to be built
■ binary directory: directory under which all files generated by build
process will be placed
■ source directory must contain CMakeLists.txt file which is used to
describe build process
■ cache: file where CMake stores values of variables used for configuration
of build process (i.e., CMakeCache.txt in binary directory)
■ build-system generator: entity within CMake that produces native build
files (i.e., build files targeting particular native build tool)
■ CMake provides numerous generators (e.g., generators for Unix Make,
Apple Xcode, and Microsoft Visual Studio)
■ build configuration: description of build to be performed with particular
set of parameters (e.g., optimized or debug version)
■ some generators support multiple configurations using single build
■ for generators that support only single configuration, need to specify
which configuration to build
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 2654
In-Source Versus Out-of-Source Builds
■ in-source build: when binary directory chosen to be inside source tree
(e.g., same as source directory)
■ out-of-source build: when binary directory chosen to be outside source
tree
■ when out-of-source build used, contents of source directory not modified
in any way by build process
■ in contrast, when in-source build used, build process can generate many
new files under source directory
■ out-of-source builds have numerous advantages over in-source builds; in
particular, out-of-source builds:
2 avoid cluttering source tree with many files generated by build process,
which can cause numerous difficulties (e.g., interacting poorly with version
control systems)
2 facilitate easy removal of all files generated by build process without risk of
accidentally removing source files
2 allow for multiple builds from single source tree (e.g., debug and release
builds)
■ for above reasons, in-source builds should generally be avoided
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 2655
The cmake Command for Configuring
■ To generate build files for a native build tool (i.e., configure), use a
command of the form:
cmake [options] [$srcdir]
■ The source directory $srcdir may be optionally specified.
■ The source and binary directories default to the current directory (resulting
in an in-source build), but may both be set by using the -S and -B options.
■ Some options include:
Option Description
-S srcdir Set the source directory to srcdir.
-B bindir Set the binary directory to bindir.
-D var=val Set the CMake variable var to val.
-G gen Set the build-system generator to gen.
--version Print name/version banner and exit.
--help Print usage information and exit.
--debug-output Enable debugging output.
--trace Enable tracing output.
2 hello.cpp
$SOURCE_DIR/CMakeLists.txt
1 # Specify minimum required version of CMake.
2 cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
3
4 # Specify project and identify languages used.
5 project(hello LANGUAGES CXX)
6
7 # Add program target called hello.
8 add_executable(hello hello.cpp)
CMakeLists Files
■ C++ compiler:
2 CMAKE_CXX_COMPILER_ID. The C++ compiler in use (e.g., Clang, GNU,
Intel, MSVC).
2 CMAKE_CXX_STANDARD. Used to initialize the CXX_STANDARD property on all
targets, which selects version of C++ standard (e.g., 98, 11, and 14).
2 CMAKE_CXX_STANDARD_REQUIRED. Used to initialize the
CXX_STANDARD_REQUIRED property of all targets. This property determines
whether the specified version of C++ standard is required.
2 CMAKE_CXX_COMPILER. The compiler command used for C++ source code.
2 CMAKE_CXX_FLAGS. The compiler flags for compiling C++ source code.
2 CMAKE_CXX_FLAGS_DEBUG. The compiler flags for compiling C++ source
code for a debug build.
2 CMAKE_CXX_FLAGS_RELEASE. The compiler flags for compiling C++ source
code for a release build.
2 CMAKE_CXX_FLAGS_RELWITHDEBINFO. The compiler flags for compiling
C++ source code for a release build with debug flags.
2 CMAKE_CXX_FLAGS_MINSIZEREL. The compiler flags for compiling C++
source code for a release build with minimum code size.
■ Linker:
2 CMAKE_EXE_LINKER_FLAGS. The linker flags used to create executables.
Windows, Darwin).
2 UNIX. Specifies if the target system’s OS is UNIX (or UNIX-like).
2 APPLE. Specifies if the target system’s OS is Mac OS X.
2 WIN32. Specifies if the target system’s OS is Microsoft Windows (32- or
64-bit).
builds.
2 CMAKE_RULE_MESSAGES. Specify if a progress message should be reported
by each makefile rule.
■ Other:
2 CMAKE_MODULE_PATH. The list of directories to search for CMake modules.
■ Initialization:
2 cmake_minimum_required. Set the minimum required version of Cmake
for a project.
2 cmake_policy. Manage CMake policy settings. (This is used to select
between old and new behaviors in CMake.)
2 project. Set a name, version, and enable languages for the entire project.
(If no languages specified, defaults to C and C++.)
2 option. Provide an option that the user can (optionally) select.
■ Adding targets:
2 add_executable. Add a program target.
CTest.)
2 add_custom_target. Add a target with no output file that is always out of
date.
2 add_custom_command. Add a custom build rule to the generated build
system.
for linking a target. (May be used multiple times for the same target.)
2 set_target_properties. Set properties for a target. (Some properties
include: OUTPUT_NAME, SOVERSION, and VERSION.)
in a list.
2 while and endwhile. Evaluate a group of commands while a condition is
true.
2 function and endfunction. Record a function for later invocation as a
command.
2 macro and endmacro. Record a macro for later invocation as a command.
■ Other:
2 message. Display a message to the user.
2 configure_file. Copy a file to another location and modify its contents.
2 install. Specify rules to run at install time (e.g., rules to install programs,
Boost_LIBRARIES
2 imported targets: Boost::boost, Boost::component
■ Doxygen
2 https://cmake.org/cmake/help/v3.10/module/FindDoxygen.html
2 variables: DOXYGEN_FOUND, DOXYGEN_EXECUTABLE
2 imported targets: Doxygen::doxygen, Doxygen::dot
Examples
hello.cpp
1 #include <iostream>
2
3 int main() {std::cout << "Hello, World!\n";}
CMakeLists.txt
1 # Specify minimum required version of CMake.
2 cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
3
4 # Specify project and identify languages used.
5 project(hello LANGUAGES CXX)
6
7 # Print message indicating detected OS.
8 if (UNIX)
9 set(platform "Unix")
10 elseif (WIN32)
11 set(platform "Microsoft Windows")
12 else()
13 set(platform "Unknown")
14 endif()
15 message("OS is ${platform}")
16
17 # Add program target called hello.
18 add_executable(hello hello.cpp)
■ project has:
2 executable target hello
2 test target run_test
hello.cpp
1 #include <iostream>
2
3 int main() {std::cout << "Hello, World!\n";}
run_test
1 #! /bin/sh
2 # Test if the hello program produces the desired output.
3 ($CMAKE_BINARY_DIR/hello | grep "^Hello, World!$") || \
4 exit 1
■ project has:
2 executable target hello
CMakeLists.txt
1 cmake_minimum_required(VERSION 3.4 FATAL_ERROR)
2 project(threads_example LANGUAGES CXX)
3
4 # Require compliance with C++11 standard.
5 set(CMAKE_CXX_STANDARD 11)
6 set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
7
8 # Find the threads library, indicating a preference for the
9 # pthread library.
10 set(THREADS_PREFER_PTHREAD_FLAG ON)
11 find_package(Threads REQUIRED)
12
13 # Define a program target called hello.
14 add_executable(hello hello.cpp)
15
16 # Set the libraries for the hello target.
17 target_link_libraries(hello Threads::Threads)
■ project has:
2 executable target my_app
main.cpp
1 #include <boost/log/trivial.hpp>
2
3 int main() {
4 BOOST_LOG_TRIVIAL(warning)
5 << "A warning severity message";
6 BOOST_LOG_TRIVIAL(error)
7 << "An error severity message";
8 BOOST_LOG_TRIVIAL(fatal)
9 << "A fatal severity message";
10 }
CMakeLists.txt
1 cmake_minimum_required(VERSION 3.4 FATAL_ERROR)
2 project(boost_example LANGUAGES CXX)
3
4 # Find the required libraries (i.e., POSIX threads and Boost).
5 set(Boost_USE_MULTITHREADED ON)
6 find_package(Threads REQUIRED)
7 find_package(Boost 1.54.0 REQUIRED COMPONENTS log)
8
9 # Define a program target called my_app.
10 add_executable(my_app main.cpp)
11
12 # Set the includes, defines, and libraries for the my_app target.
13 target_include_directories(my_app PUBLIC ${Boost_INCLUDE_DIRS})
14 target_compile_definitions(my_app PUBLIC "-DBOOST_LOG_DYN_LINK")
15 target_link_libraries(my_app ${Boost_LIBRARIES}
16 ${CMAKE_THREAD_LIBS_INIT})
CMakeLists.txt
1 cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
2 project(boost_example LANGUAGES CXX)
3
4 # Find the required libraries (i.e., POSIX threads and Boost).
5 set(Boost_USE_MULTITHREADED ON)
6 find_package(Threads REQUIRED)
7 find_package(Boost 1.54.0 REQUIRED COMPONENTS log)
8
9 # Define a program target called my_app.
10 add_executable(my_app main.cpp)
11
12 # Set the defines, includes, and libraries for the my_app target.
13 target_compile_definitions(my_app PUBLIC "-DBOOST_LOG_DYN_LINK")
14 target_link_libraries(my_app Boost::log Threads::Threads)
■ project has:
2 executable target trivial
CMakeLists.txt
1 cmake_minimum_required(VERSION 3.2 FATAL_ERROR)
2 project(opengl_example LANGUAGES CXX)
3 set(CMAKE_CXX_STANDARD 11)
4 set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
5
6 # Find the required libraries (i.e., OpenGL, GLEW, and GLFW).
7 find_package(OpenGL REQUIRED)
8 find_package(GLEW REQUIRED)
9 find_package(PkgConfig REQUIRED)
10 pkg_search_module(GLFW REQUIRED glfw3)
11
12 # Define a program target called trivial.
13 add_executable(trivial trivial.cpp)
14
15 # Set the includes and libraries for the trivial target.
16 target_include_directories(trivial PUBLIC ${GLFW_INCLUDE_DIRS}
17 ${GLEW_INCLUDE_DIRS} ${OPENGL_INCLUDE_DIR})
18 target_link_libraries(trivial ${GLFW_LIBRARIES} ${GLEW_LIBRARIES}
19 ${OPENGL_LIBRARIES})
CMakeLists.txt
1 cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
2 project(opengl_example LANGUAGES CXX)
3 set(CMAKE_CXX_STANDARD 11)
4 set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
5
6 # Find the required libraries (i.e., OpenGL, GLEW, and GLFW).
7 find_package(OpenGL REQUIRED)
8 find_package(GLEW REQUIRED)
9 find_package(PkgConfig REQUIRED)
10 pkg_search_module(GLFW REQUIRED glfw3)
11
12 # Define a program target called trivial.
13 add_executable(trivial trivial.cpp)
14
15 # Set the includes and libraries for the trivial target.
16 target_include_directories(trivial PUBLIC ${GLFW_INCLUDE_DIRS})
17 target_link_libraries(trivial ${GLFW_LIBRARIES} GLEW::GLEW
18 OpenGL::GL)
■ project has:
2 executable target trivial
trivial.cpp
1 #include <GL/glut.h>
2
3 void display() {
4 glClearColor(0.0, 1.0, 1.0, 0.0);
5 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
6 glutSwapBuffers();
7 }
8
9 int main(int argc, char** argv) {
10 glutInit(&argc, argv);
11 glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
12 glutInitWindowSize(512, 512);
13 glutCreateWindow(argv[0]);
14 glutDisplayFunc(display);
15 glutMainLoop();
16 }
CMakeLists.txt
1 cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
2 project(opengl_example LANGUAGES CXX)
3
4 # Find the required libraries (i.e., OpenGL and GLUT).
5 find_package(OpenGL REQUIRED)
6 find_package(GLUT REQUIRED)
7
8 # Define a program target called trivial.
9 add_executable(trivial trivial.cpp)
10
11 # Set the includes and libraries for the trivial target.
12 target_include_directories(trivial PUBLIC ${GLUT_INCLUDE_DIR}
13 ${OPENGL_INCLUDE_DIR})
14 target_link_libraries(trivial ${GLUT_LIBRARIES} ${OPENGL_LIBRARIES})
CMakeLists.txt
1 cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
2 project(opengl_example LANGUAGES CXX)
3
4 # Find the required libraries (i.e., OpenGL and GLUT).
5 find_package(OpenGL REQUIRED)
6 find_package(GLUT REQUIRED)
7
8 # Define a program target called trivial.
9 add_executable(trivial trivial.cpp)
10
11 # Set the includes and libraries for the trivial target.
12 target_link_libraries(trivial GLUT::GLUT OpenGL::GL)
■ project has:
2 executable target orient_test
CMakeLists.txt
1 # Specify minimum required version of CMake.
2 cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
3
4 # Specify project and enable the C++ language.
5 project(cgal_demo LANGUAGES CXX)
6
7 # Find the required CGAL package.
8 find_package(CGAL REQUIRED)
9
10 # On some systems, GCC may need the -frounding-math option.
11 if (CMAKE_CXX_COMPILER_ID MATCHES GNU)
12 add_compile_options("-frounding-math")
13 endif()
14
15 # Add a program target called orient_test.
16 add_executable(orient_test orient_test.cpp)
17
18 # Specify the includes and libraries for the orient_test target.
19 target_include_directories(orient_test PUBLIC ${CGAL_INCLUDE_DIRS})
20 target_link_libraries(orient_test ${CGAL_LIBRARY} ${GMP_LIBRARIES})
■ want to be able to build and install HG2G library and application that uses
library
■ code written in C++
■ files in project:
2 CMakeLists.txt
2 app/CMakeLists.txt
2 app/answer.cpp
2 hg2g/CMakeLists.txt
2 hg2g/answer.cpp
2 hg2g/question.cpp
2 hg2g/include/hg2g/answer.hpp
2 hg2g/include/hg2g/config.hpp.in
■ project has:
2 library target hg2g
2 executable target answer
2 option HG2G_ZAPHOD (which takes a boolean value)
app/answer.cpp
1 #include <iostream>
2 #include <hg2g/config.hpp>
3 #include <hg2g/answer.hpp>
4
5 int main() {
6 #ifdef HG2G_ZAPHOD
7 std::cout << "HG2G_ZAPHOD is defined\n";
8 #endif
9 std::cout << "According to version " << HG2G_VERSION <<
10 " of the HG2G library:\n";
11 std::cout <<
12 "The answer to the ultimate question is " <<
13 hg2g::answer_to_ultimate_question() << ".\n";
14 }
app/CMakeLists.txt
1 # Add a program target called answer.
2 add_executable(answer answer.cpp)
3
4 # Link the answer program against the hg2g library.
5 target_link_libraries(answer hg2g)
6
7 # Install the answer program in the bin directory.
8 install(TARGETS answer DESTINATION bin)
hello, hg2g, and example 100 are subdirectories containing CMake projects
CMakeLists.txt
1 cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
2 # Specify the project and do not enable any languages.
3 project(examples LANGUAGES CXX)
4 # Include the module for external project functionality.
5 include(ExternalProject)
6 # Create a list of the subdirectories containing
7 # CMake projects to be built.
8 list(APPEND dirs hello hg2g "example 100")
9 # Add each project as an external project.
10 foreach(dir IN LISTS dirs)
11 # Set target name to directory name with any
12 # spaces changed to underscores.
13 string(REPLACE " " "_" target "${dir}")
14 # Add external project.
15 externalproject_add("${target}"
16 SOURCE_DIR "${CMAKE_SOURCE_DIR}/${dir}"
17 BINARY_DIR "${CMAKE_BINARY_DIR}/${dir}"
18 CMAKE_ARGS
19 "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
20 INSTALL_COMMAND "")
21 endforeach()
■ want to build LATEX document (i.e., produce PDF document from LATEX
source)
■ files in project:
2 CMakeLists.txt
2 main.tex
2 bib.bib
2 cmake_modules/UseLATEX.cmake
bib.bib
1 @book{
2 TCPL4,
3 author = "B. Stroustrup",
4 title = "The {C++} Programming Language",
5 edition = "4th",
6 publisher = "Addison Wesley",
7 year = 2013
8 }
CMakeLists.txt
1 cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
2
3 # Specify the project name and indicate that no languages
4 # should be enabled.
5 project(my_doc NONE)
6
7 # Add the cmake_modules directory to the module search path.
8 set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
9 ${CMAKE_SOURCE_DIR}/cmake_modules)
10
11 # Include the UseLATEX module.
12 include(UseLATEX)
13
14 # Specify the properties of the LaTeX document such as its
15 # constituent source files (e.g., LaTeX, BibTeX, images,
16 # figures, etc.)
17 add_latex_document(main.tex IMAGES cpp.png BIBFILES bib.bib)
cmake_modules/UseLATEX.cmake
This file is taken from https://cmake.org/Wiki/images/8/80/
UseLATEX.cmake.
■ want to provide file that can be included in CMakeLists file that defines
option called ENABLE_PROFILING for building code with profiling enabled
■ for sake of simplicity, only consider cases of using GCC and Clang C++
compilers
■ place definition of option in file named profiling.cmake
■ include profiling.cmake in CMakeLists.txt
profiling.cmake
1 # Define an option for enabling code profiling, which is disabled
2 # by default.
3 option(ENABLE_PROFILING "Enable code profiling with gprof." false)
4
5 if (ENABLE_PROFILING)
6 if (CMAKE_CXX_COMPILER_ID MATCHES GNU OR
7 CMAKE_CXX_COMPILER_ID MATCHES Clang)
8 # The GCC or Clang C++ compiler is being used.
9 # Add the -pg option to the flags used for compiling.
10 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
11 # Add the -pg option to the linker flags used for creating
12 # executables.
13 set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
14 # Add the -pg option to the linker flags used for creating
15 # shared libraries.
16 set(CMAKE_SHARED_LINKER_FLAGS
17 "${CMAKE_SHARED_LINKER_FLAGS} -pg")
18 else()
19 # Handle the case of unsupported compilers.
20 message(FATAL_ERROR
21 "Only GCC and Clang are currently supported.")
22 endif()
23 endif()
References
1 Bill Hoffman. Google Tech Talk — Building Science With CMake. October
8, 2015. Available online at https://youtu.be/TqjtN8NGtl4.
A very basic introduction to CMake.
2 Daniel Pfeifer. Effective CMake. C++ Now, May 19, 2017, Aspen, CO,
USA. Available online at https://youtu.be/bsXLMQ6WgIk.
3 Florent Castelli. Introduction to CMake. SwedenCpp::Stockholm 0xC,
Sundbyberg, Sweden, Apr. 26, 2018. Available online at
https://youtu.be/jt3meXdP-QI.
4 Deniz Bahadir. More Modern CMake — Working With CMake 3.12 and
Later. Meeting C++, Berlin, Germany, Nov. 16, 2018. Available online at
https://youtu.be/TsddSCzYiRs.
Server
Repository
Repository
Git
2 NVIDIA (https://github.com/NVIDIA)
2 Twitter (https://github.com/twitter)
2 Android (https://android-review.googlesource.com)
2 Qt (http://code.qt.io)
2 Gnome (https://git.gnome.org)
2 Eclipse (https://git.eclipse.org)
2 KDE (https://github.com/KDE)
2 FreeDesktop (https://cgit.freedesktop.org)
C3 C5
master
M1 M2 M3 M4 M5 M6
master
M1 M2 develop
D1 D2 D3 topic
T1 T2
Checkout
Stage Commit
Working Tree Index Repository
repository
2 staging: applies changes in working tree to index
2 committing: applies changes in index to repository
Fetch/Pull
Push
Local Repository Remote Repository
merging changes
2 pull: propagate changes from remote repository to local repository and
merge changes
C1 C2 C3
C1 C2 C3
[remote repository]
■ Cloning the above repository will produce a new local repository whose
commit history is as shown below, with a (local) branch master and a
remote-tracking branch origin/master.
HEAD master
C1 C2 C3
origin/master
[local repository]
■ A branch fetched from a remote repository is called a remote-tracking
branch.
■ A remote-tracking branch is a reference to a commit in the remote
repository and is used for operations like pushing and fetching/pulling.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 2732
Commit History Example I
C1 C2
2 Cloning the remote repository yields a new local repository that is identical
to the remote repository but with a remote-tracking branch
origin/master added as follows:
HEAD master
C1 C2
origin/master
C1 C2 −→ C1 C2 A1
origin/master origin/master
C1 C2 A1 −→ C1 C2 A1 A2
origin/master origin/master
−→
C1 C2 C1 C2 B1 B2
6 Fetching (to the local repository) from the remote repository transforms
the local repository as follows:
HEAD master
HEAD master
C1 C2 A1 A2
C1 C2 A1 A2 −→
B1 B2
origin/master
origin/master
8 Pushing (from the local repository) to the remote repository transforms the
remote and local repositories, respectively, as follows:
HEAD master C1 C2 A1 A2 A3
−→
C1 C2 B1 B2 B1 B2 master HEAD
C1 C2 A1 A2 A3 origin/master
B1 B2 master −→ C1 C2 A1 A2 A3
HEAD
origin/master B1 B2 master HEAD
1 TOP_DIR=‘pwd‘
2
3 cd $TOP_DIR
4 mkdir remote
5 cd remote
6 git init
7 printf "apple\n" >> fruits.txt
8 git add fruits.txt
9 git commit -m "Added file fruits.txt" # Commit C1
10 printf "banana\n" >> fruits.txt
11 git add fruits.txt
12 git commit -m "Added banana to fruits.txt" # Commit C2
13 git init --bare .git
14 mv .git $TOP_DIR/remote.git
15 cd $TOP_DIR
16 rm -rf remote
Basic Commands
■ To set the variable $name to the value $value, use a command of the
form:
git config [options] $name $value
■ To unset the variable $name, use a command of the form:
git config [options] --unset $name
■ To list all of the current variables settings, use a command of the form:
git config [options] -l
■ Some commonly-used options include:
Option Description
--system consider only the system-wide settings
--global consider only the global (i.e., per-user) settings
--local consider only the local (i.e., per-repository) settings
Variable Description
core.askPass program for entering user name and password
credentials
core.editor program for editing
core.pager program for paging output
credential.helper external program to be called when a user
name or password credential is needed
user.name user’s full name
user.email user’s email address
web.browser program for browsing web
■ To remove the files/directories $path... from the working tree and the
index, use a command of the form:
git rm [options] $path...
■ Some commonly-used options include:
Option Description
-f override the up-to-date check
-r if the given path is a directory, recursively remove files be-
low it
■ To remove the directory src and all files and directories beneath it from
the working tree and index, type:
git rm -r src
■ To remove the files README and LICENSE from the working tree and index,
type:
git rm README LICENSE
■ To display the status of the working tree, use a command of the form:
git [options] status
■ Some commonly-used options include:
Option Description
--long give the output in long format (default)
--short give the output in short format
■ The information displayed by this command includes:
2 paths (i.e., files and directories) that have differences between the index
and the current HEAD commit, (i.e., what would be committed by running
git commit)
2 paths that have differences between the working tree and index as well as
paths that are not tracked by Git (i.e., what could by committed by running
git add before git commit)
■ To display the status of the working tree in long form, type:
git status
■ To show the commit history for the file README since 2016-01-01, type:
git log --since 2016-01-01 README
■ To show the commit history for all files between 2014-01-01 and
2014-12-31, type:
git log --since 2014-01-01 --until 2014-12-31
■ To show the commit history for all files in all branches with a text-based
graph, type:
git log --all --graph
■ To show the commit history for all commits made since v1.0 until and
including v2.0 (assuming that v1.0 and v2.0 exist), type:
git log v1.0..v2.0
■ To search for the text “hello” in a case insensitive manner in all of the
files in the working tree, type:
git grep -i -e hello
■ To print only the names of the files that match the pattern specified in the
preceding example, type:
git grep -i -e hello -l
■ To find all of the files in the working tree with suffixes “.cpp” or “.hpp” that
have lines containing either “#include <vector>” or
“#include <list>”, type:
git grep -e ’#include <vector>’ --or \
-e ’#include <list>’ -- ’*.cpp’ ’*.hpp’
■ To perform the same search as the preceding example but in the index
rather than the working tree, type:
git grep --cache -e ’#include <vector>’ --or \
-e ’#include <list>’ -- ’*.cpp’ ’*.hpp’
Remote-Related Commands
■ To pull changes from the branch $branch of the remote $remote, use a
command of the form:
git pull [$remote [$branch]]
■ To pull from the default remote and branch, type:
git pull
■ A pull is approximately a fetch followed by merge.
■ A pull automatically merges commits without letting them be reviewed first.
■ For this reason, some people suggest that it is better to use fetch and
merge separately instead of performing a pull.
■ Also, the use of pull operations can, in some cases, result in unnecessary
merge commits.
Branch-Related Commands
■ To checkout (i.e., switch to) the branch $branch, use a command of the
form:
git checkout $branch
■ Checking out a branch changes the files/directories of the working tree to
match that branch.
■ If you have local modifications to one or more files that are different
between the current branch and the branch to which you are switching,
the command refuses to switch branches in order to preserve your
modifications in context.
Tag-Related Commands
■ To push a tag $tag to the remote $remote, use a command of the form:
git push $remote $tag
■ To push the tag v1.0 to the remote origin, type:
git push origin v1.0
Miscellany
■ can create exact duplicate of entire Git repository (including all tags and
local branches) by using bare-clone and mirror-push operations
■ to copy repository $source_repo to (already existing) remote repository
$destination_repo (overwriting contents of repository), use command
sequence:
# Create a bare clone of the repository.
git clone --bare $source_repo $bare_dir
# Mirror push to the destination repository.
git -C $bare_dir push --mirror $destination_repo
# Remove the temporary local bare repository.
rm -rf $bare_dir
■ to copy repository $source_repo to local repository directory
$destination_dir, simply perform bare clone operation using
command:
git clone --bare $source_repo $destination_dir
References
1 Linus Torvalds. Google Tech Talk: Linus Torvalds on git — Git: Source
code control the way it was meant to be!. May 2007. Available online at
https://youtu.be/4XpnKHJAok8.
Linus Torvalds shares his thoughts on Git, the source control management
system he created.
2 Matthew McCullough. The Basics of Git and GitHub. July 2013. Available
online at https://youtu.be/U8GBXvdmHT4.
This is an excellent introduction to using Git.
3 Scott Chacon. Introduction to Git with Scott Chacon of GitHub. June
2011. Available online at https://youtu.be/ZDR433b0HJY.
This is another popular introduction to using Git.
4 Matthew McCullough. Advanced Git: Graphs, Hashes, and Compression,
Oh My!. Sept. 2012. Available online at
https://youtu.be/ig5E8CcdM9g.
This is a very good more advanced talk on Git.
Miscellaneous Tools
2 Ideone
2 https://ideone.com
2 Coliru
2 http://coliru.stacked-crooked.com
2 Repl.It
2 https://repl.it/repls/SmallIvorySyntax
2 Compiler
. . . . . . . . . . . .Explorer
. . . . . . . . . (discussed in more detail shortly)
2 C++
. . . . . .Insights
. . . . . . . . . (discussed in more detail shortly)
Clang Format
YouCompleteMe (YCM)
Miscellany
Miscellany
■ The C++ implementation is permitted (but not required) to place the C abs
function in the global namespace.
■ If the implementation does not do this, the above program will fail to
compile (avoiding the more troubling problem discussed next).
■ If, however, the C++ implementation does do this (which is not uncommon
in practice), the above program will compile successfully, but behave
unexpectedly when run.
■ In particular, the program will output a value of 1, instead of the value of
1.5 that was likely expected by the programmer.
■ Since the C abs function is declared as int abs(int), the use of this
function will introduce a conversion from double to int, leading to the
unexpected result.
Copyright © 2015–2021 Michael D. Adams Programming in C++ Version 2021-04-01 2821
The abs Function (Continued)
■ Fix:
std::string join(std::string&& s, const char* p) {
return std::move(s.append(", ").append(p));
}
1 #include <cassert>
2
3 int main() {
4 short ss = -1;
5 int si = -1;
6 long sl = -1;
7 long long sll = -1;
8 unsigned short us = 0;
9 unsigned int ui = 0;
10 unsigned long ul = 0;
11 unsigned long long ull = 0;
12 // comparison between signed and unsigned types
13 assert(ss < ui); // FAILS: ss becomes UINT_MAX
14 // comparison between signed and unsigned types
15 assert(si < ui); // FAILS: si becomes UINT_MAX
16 // comparison between signed and unsigned types
17 assert(sl < ul); // FAILS: sl becomes ULONG_MAX
18 // comparison between signed and unsigned types
19 assert(sll < ull); // FAILS: sll becomes ULONGLONG_MAX
20 }
Exercises
foo.hpp
1 #ifndef foo_hpp
2 #define foo_hpp
3 namespace foo {
4 bool is_odd(int x) {return (x % 2) != 0;}
5 bool is_even(int x) {return (x % 2) == 0;}
6 }
7 #endif
main.cpp
1 #include <iostream>
2 #include "foo.hpp"
3 int main() {
4 std::cout << foo::is_odd(42) << ’ ’ <<
5 foo::is_even(42) << ’\n’;
6 }
other.cpp
1 #include "foo.hpp"
2 // ...
foo.hpp
1 #ifndef foo_hpp
2 #define foo_hpp
3 namespace foo {
4 inline bool is_odd(int x) {return (x % 2) != 0;}
5 inline bool is_even(int x) {return (x % 2) == 0;}
6 }
7 #endif
main.cpp
1 #include <iostream>
2 #include "foo.hpp"
3 int main() {
4 std::cout << foo::is_odd(42) << ’ ’ <<
5 foo::is_even(42) << ’\n’;
6 }
other.cpp
1 #include "foo.hpp"
2 // ...
foo.hpp
1 #ifndef foo_hpp
2 #define foo_hpp
3 namespace foo {
4 inline bool is_odd(int x);
5 inline bool is_even(int x);
6 }
7 #endif
foo.cpp
1 #include "foo.hpp"
2 namespace foo {
3 bool is_odd(int x) {return (x % 2) != 0;}
4 bool is_even(int x) {return (x % 2) == 0;}
5 }
app.cpp
1 #include <iostream>
2 #include "foo.hpp"
3 int main() {
4 std::cout << foo::is_odd(42) << ’ ’ <<
5 foo::is_even(42) << ’\n’;
6 }
foo.hpp
1 #ifndef foo_hpp
2 #define foo_hpp
3 namespace foo {
4 inline bool is_odd(int x) {return (x % 2) != 0;}
5 inline bool is_even(int x) {return (x % 2) == 0;}
6 }
7 #endif
app.cpp
1 #include <iostream>
2 #include "foo.hpp"
3 int main() {
4 std::cout << foo::is_odd(42) << ’ ’ <<
5 foo::is_even(42) << ’\n’;
6 }
foo.hpp
1 #ifndef foo_hpp
2 #define foo_hpp
3 namespace foo {
4 template <typename T> T abs(const T& x);
5 }
6 #endif
foo.cpp
1 #include "foo.hpp"
2 namespace foo {
3 template <typename T> T abs(const T& x)
4 {return (x < 0) ? (-x) : x;}
5 }
app.cpp
1 #include <iostream>
2 #include "foo.hpp"
3 int main() {
4 std::cout << foo::abs(-42) << ’ ’ <<
5 foo::abs(-3.14) << ’\n’;
6 }
foo.hpp
1 #ifndef foo_hpp
2 #define foo_hpp
3 namespace foo {
4 template <typename T> T abs(const T& x);
5 }
6 #endif
foo.cpp
1 #include "foo.hpp"
2 namespace foo {
3 template <typename T> T abs(const T& x)
4 {return (x < 0) ? (-x) : x;}
5 template int abs<int>(const int&);
6 template double abs<double>(const double&);
7 }
app.cpp
1 #include <iostream>
2 #include "foo.hpp"
3 int main() {
4 std::cout << foo::abs(-42) << ’ ’ <<
5 foo::abs(-3.14) << ’\n’;
6 }
foo.hpp
1 #ifndef foo_hpp
2 #define foo_hpp
3 namespace foo {
4 template <typename T> T abs(const T& x)
5 {return (x < 0) ? (-x) : x;}
6 }
7 #endif
app.cpp
1 #include <iostream>
2 #include "foo.hpp"
3 int main() {
4 std::cout << foo::abs(-42) << ’ ’ <<
5 foo::abs(-3.14) << ’\n’;
6 }
foo.hpp
1 #ifndef foo_hpp
2 #define foo_hpp
3 #include <cmath>
4 namespace foo {
5 double log(double x, double b);
6 }
7 #endif
foo.cpp
1 #include <cmath>
2 #include "foo.hpp"
3 namespace foo {
4 double log(double x, double b = 10.0)
5 {return std::log(x) / std::log(b);}
6 }
app.cpp
1 #include <iostream>
2 #include "foo.hpp"
3 int main() {
4 std::cout << foo::log(16.0, 2.0) << ’ ’ <<
5 foo::log(10.0) << ’\n’;
6 }
foo.cpp
1 #include <cmath>
2 #include "foo.hpp"
3 namespace foo {
4 double log(double x, double b)
5 {return std::log(x) / std::log(b);}
6 }
app.cpp
1 #include <iostream>
2 #include "foo.hpp"
3 int main() {
4 std::cout << foo::log(16.0, 2.0) << ’ ’ <<
5 foo::log(10.0) << ’\n’;
6 }
foo.hpp
1 namespace foo {
2 constexpr int abs(int x);
3 }
foo.cpp
1 #include "foo.hpp"
2
3 namespace foo {
4 constexpr int abs(int x) {return (x < 0) ? (-x) : x;}
5 }
app.cpp
1 #include <iostream>
2 #include "foo.hpp"
3
4 int main() {
5 constexpr auto a = foo::abs(-42);
6 std::cout << a << ’\n’;
7 }
foo.hpp
1 namespace foo {
2 constexpr int abs(int x) {return (x < 0) ? (-x) : x;}
3 }
app.cpp
1 #include <iostream>
2 #include "foo.hpp"
3
4 int main() {
5 constexpr auto a = foo::abs(-42);
6 std::cout << a << ’\n’;
7 }
1 #include <cmath>
2 #include <cassert>
3
4 constexpr double func(double x) {
5 assert(x >= 0.0);
6 return std::sqrt(x) + std::sin(x) * std::cos(x);
7 }
1 #include <cmath>
2 #include <cassert>
3
4 constexpr double func(double x) {
5 assert(x >= 0.0);
6 return std::sqrt(x) + std::sin(x) * std::cos(x);
7 }
1 #include <list>
2 #include <iostream>
3
4 constexpr std::list<int> get_values() {
5 return {1, 2, 3};
6 }
7
8 int main() {
9 for (auto x : get_values()) {
10 std::cout << x << ’\n’;
11 }
12 }
1 #include <list>
2 #include <iostream>
3
4 // ERROR: std::list<int> is not literal type
5 // could instead use return type of std::array<int, 3>
6 constexpr std::list<int> get_values() {
7 return {1, 2, 3};
8 }
9
10 int main() {
11 for (auto x : get_values()) {
12 std::cout << x << ’\n’;
13 }
14 }
1 #include <vector>
2 #include <iostream>
3
4 constexpr std::vector<int>
5 get_values(int a, int b, int c) {
6 return {a, b, c};
7 }
8
9 int main() {
10 constexpr auto v = get_values(1, 2, 3);
11 for (auto x : v) {
12 std::cout << x << ’\n’;
13 }
14 }
1 #include <vector>
2 #include <iostream>
3
4 constexpr std::vector<int>
5 get_values(int a, int b, int c) {
6 return {a, b, c};
7 }
8
9 int main() {
10 // ERROR: initializer for v is not constant expression due
11 // to constructor of std::vector<int> allocating memory
12 // (via new) that is not freed during evaluation of
13 // initializer expression; to fix this problem, could change
14 // return type of get_values to std::array<int, 3>
15 // or remove constexpr from declaration of v
16 constexpr auto v = get_values(1, 2, 3);
17 for (auto x : v) {
18 std::cout << x << ’\n’;
19 }
20 }
1 #include <algorithm>
2 #include <cstddef>
3
4 template <class T>
5 void in_place_transpose(T* a, std::size_t m, std::size_t n) {
6 T tmp[m][n];
7 std::copy_n(a, m * n, &tmp[0][0]);
8 for (std::size_t i = 0; i < m; ++i) {
9 for (std::size_t j = 0; j < n; ++j) {
10 a[j * m + i] = tmp[i][j];
11 }
12 }
13 }
■ ignore possibility of integer overflow
■ ignore potential cache-efficiency issues
1 #include <algorithm>
2 #include <vector>
3 #include <cstddef>
4
5 template <class T>
6 void in_place_transpose(T* a, std::size_t m, std::size_t n) {
7 std::vector<T> tmp(a, a + m * n);
8 for (std::size_t i = 0; i < m; ++i) {
9 for (std::size_t j = 0; j < n; ++j) {
10 a[j * m + i] = tmp[i * n + j];
11 }
12 }
13 }
■ use of std::vector for temporary array avoids need for VLA
■ since std::vector allocates its underlying array storage from heap,
potential stack overflow problem avoided
1 int func() {
2 int i = 0;
3 // ... (valid code for constexpr function)
4 return i;
5 }
6
7 int main() {
8 return func();
9 }
1 void func() {
2 // ... (valid code for constexpr function)
3 }
4
5 int main() {
6 func();
7 }
1 #include <cstddef>
2
3 // write elements of source array src to destination
4 // array dst in reverse order
5 template <class T, std::size_t N>
6 void reverse(const T (&src)[N], T (&dst)[N]) {
7 for (std::size_t i = 0; i < N; ++i) {
8 dst[N - 1 - i] = src[i];
9 }
10 }
1 #include <iostream>
2
3 // include definition of reverse
4 #include "aliasing_1_1.hpp"
5
6 int main() {
7 int a[4] = {1, 2, 3, 4};
8 reverse(a, a);
9 for (auto&& x : a) {
10 std::cout << x << ’\n’;
11 }
12 }
■ reverse will not work correctly if src and dst parameters refer to same
array
■ consider above usage of reverse, which is problematic
1 #include <cstddef>
2
3 template <class T, std::size_t N>
4 void reverse(const T (&src)[N], T (&dst)[N]) {
5 // Check for aliasing case.
6 if (src == dst) {
7 for (std::size_t i = 0; i < N / 2; ++i) {
8 using std::swap;
9 swap(dst[i], dst[N - 1 - i]);
10 }
11 } else {
12 for (std::size_t i = 0; i < N; ++i) {
13 dst[N - 1 - i] = src[i];
14 }
15 }
16 }
1 #include <iostream>
2
3 char* string_concat(char *, const char*);
4
5 int main() {
6 char a[1024] = "bye";
7 string_concat(a, a);
8 std::cout << a << ’\n’;
9 }
■ string_concat will not work correctly if source and destination strings
overlap
■ consider above code, which will not work correctly (and probably result in
segmentation fault)
■ best solution is to change interface so that overlap of source and
destination forbidden (since detecting aliasing cannot be done efficiently)
1 class Complex {
2 public:
3 Complex(double r, double i) : r_(r), i_(i) {}
4 double real() const {return r_;}
5 double imag() const {return i_;}
6 // What is wrong with the following function?
7 Complex& operator*=(const Complex& other) {
8 auto r = r_;
9 auto i = i_;
10 r_ = r * other.r_ - i * other.i_;
11 i_ = r * other.i_ + i * other.r_;
12 return *this;
13 }
14 // ...
15 private:
16 double r_; // real part
17 double i_; // imaginary part
18 };
1 #include <iostream>
2 #include "aliasing_3_1.hpp"
3
4 int main() {
5 Complex a(1.0, 1.0);
6 Complex b(a);
7 b *= a;
8 a *= a;
9 // The value of b is correct.
10 std::cout << b.real() << ’ ’ << b.imag() << ’\n’;
11 // The value of a is not correct.
12 std::cout << a.real() << ’ ’ << a.imag() << ’\n’;
13 }
■ Complex::operator*= will not correctly handle case that *this and
other are aliased
■ above code will not work correctly
1 class Complex {
2 public:
3 Complex(double r, double i) : r_(r), i_(i) {}
4 double real() const {return r_;}
5 double imag() const {return i_;}
6 Complex& operator*=(const Complex& other) {
7 auto r = r_;
8 auto i = i_;
9 auto other_r = other.r_;
10 auto other_i = other.i_;
11 r_ = r * other_r - i * other_i;
12 i_ = r * other_i + i * other_r;
13 return *this;
14 }
15 // ...
16 private:
17 double r_; // real part
18 double i_; // imaginary part
19 };
1 class Widget {
2 public:
3 Widget() {}
4 int i() const {return i_;}
5 private:
6 int i_;
7 };
8
9 int main() {
10 Widget w;
11 return w.i();
12 }
1 class Widget {
2 public:
3 Widget() : i_() {}
4 int i() const {return i_;}
5 private:
6 int i_;
7 };
8
9 int main() {
10 Widget w;
11 return w.i();
12 }
1 class Widget {
2 public:
3 Widget() {}
4 int i() const {return i_;}
5 private:
6 int i_{};
7 };
8
9 int main() {
10 Widget w;
11 return w.i();
12 }
1 #include <iostream>
2
3 // aggregate
4 struct Widget {
5 int i = 0;
6 int j = 0;
7 };
8
9 std::ostream& operator<<(std::ostream& out, const Widget& w) {
10 return out << w.i << ’,’ << w.j;
11 }
12
13 int main() {
14 Widget a;
15 Widget b{};
16 Widget c{1};
17 Widget d{1, 2};
18 std::cout << a << ’ ’ << b << ’ ’ << c << ’ ’ << d << ’\n’;
19 }
1 #include <utility>
2
3 class Gadget {
4 public:
5 Gadget();
6 Gadget(const Gadget&);
7 Gadget(Gadget&&);
8 Gadget& operator=(const Gadget&);
9 Gadget& operator=(Gadget&&);
10 // ...
11 };
12
13 Gadget func_1() {return std::move(Gadget());} // BAD IDEA
14 Gadget func_2() {Gadget g; return std::move(g);} // BAD IDEA
15 const Gadget func_3() {return Gadget();} // BAD IDEA
16 const Gadget func_4() {Gadget g; return g;} // BAD IDEA
17
18 int main() {
19 Gadget s(func_1());
20 Gadget u(func_2());
21 Gadget t(func_3());
22 Gadget w(func_4());
23 s = func_1();
24 s = func_2();
25 s = func_3();
26 s = func_4();
27 }
1 #include <utility>
2
3 class Gadget {
4 public:
5 Gadget();
6 Gadget(const Gadget&);
7 Gadget(Gadget&&);
8 Gadget& operator=(const Gadget&);
9 Gadget& operator=(Gadget&&);
10 // ...
11 };
12
13 Gadget func_1() {return std::move(Gadget());} // BAD IDEA
14 Gadget func_2() {Gadget g; return std::move(g);} // BAD IDEA
15 const Gadget func_3() {return Gadget();} // BAD IDEA
16 const Gadget func_4() {Gadget g; return g;} // BAD IDEA
17
18 int main() {
19 Gadget s(func_1()); // default ctor, move ctor
20 Gadget u(func_2()); // default ctor, move ctor
21 Gadget t(func_3()); // default ctor, copy elided
22 Gadget w(func_4()); // default ctor, maybe move ctor
23 s = func_1(); // default ctor, move ctor, move assign
24 s = func_2(); // default ctor, move ctor, move assign
25 s = func_3(); // default ctor, copy elided, copy assign
26 s = func_4(); // default ctor, maybe move ctor, copy assign
27 }
■ Unnecessary temporaries!
■ Fix:
func(s + ", " + p);
func(p + ", "s + s);
■ The const return value will interact poorly with move semantics, as the
returned object cannot be used as the source for a move operation (since
the source for a move operation must be modifiable).
■ Fix:
std::string getMessage() {
return "Hello";
}
1 CppCon, https://cppcon.org
2 C++ Now, http://cppnow.org
3 Meeting C++, https://meetingcpp.com
4 code::dive, https://codedive.pl
5 ACCU Conference, https://conference.accu.org
6 Pacific++, https://pacificplusplus.com
7 NDC Conferences, https://ndcconferences.com
2 NDC TechTown, https://ndctechtown.com
Write code!
Write lots and lots and lots of code!
■ The only way to truly learn a programming language well is to use it
heavily (i.e., write lots of code using the language).