[Book] C++ 20 for Programmers - An Objects-Natural Appraoch

First Post:

Last Update:

Word Count:
5.4k

Read Time:
33 min

El libro

Introduction

This article is used to keep notes and summaries of the book “C++ 20 for Programmers - An Objects-Natural Appraoch”.
The content will be continuously updated as I read through the book.

Reflections

  • It definitely strengthened my knowledge of modern C++(C++20).
  • There are some typos in the book.
  • Chapters 15–17 feel somewhat abstract and require more effort to fully grasp.

Chapter.8

8.7 - Find SubString in a String

1
2
3
4
5
6
7
8
9
10
std::string s = "Hello world";

s.find("l"); //Start from the beginning.
s.rfind("l"); //Start from the ending.

s.find_first_of("l");
s.find_last_of("l");
s.find_first_not_of("l");

s.erase(5); //Erase substring, start from index = 5;

8.10 - Type conversion

To Integer Return Type
stoi int
stol long
stoul unsigned long
stoll long long
stoull unsigned long long
To Float Return Type
stof float
stod double
stold long double

8.13 - ofstream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <cstdlib>
#include <fmt/format.h>
#include <fstream>
#include <iostream>
#include <string>

int main(int argc, char *argv[]) {
if (std::ofstream output{"clients.txt", std::ios::out}) {
std::cout << "Enter the account, name, and balance.\n"
<< "Enter the end-of-file to end input.\n? ";

int account;
std::string name;
double balance;

while (std::cin >> account >> name >> balance) {
output << fmt::format("{} {} {}\n", account, name, balance);
std::cout << "? ";
}
}
else
{
std::cerr << "File could not be opened\n";
std::exit(EXIT_FAILURE);
}
}
Mode Description
ios::app Appending text to the end of the file.
ios::ate Open a file for output(ofstrema), and seek to the end of the file.
ios::in Open a file for input.
ios::out Open a file for output.
ios::trunc Open a file, and truncate(discard) the content without any warning. This is also the default action of std::out.
ios::binary Open a binary file for input or output.

close:

1
output.close();

8.14 - ifstream

1
2
3
4
5
6
7
fileObject.seekg(n);

fileObject.seekg(n, ios::cur);

fileObject.seekg(n, ios::end);

fileObject.seekg(0, ios::end);

8.18 - Raw String Literal

1
std::string windowsPath{R"(C:\Windows\System32\cmd.exe)"};
1
2
3
4
5
6
R"(multiple lines
of text)"

is same as

"multiple lines\nof text"

Chapter.9

9.9 - Access the Member of class

1
2
3
4
5
6
7
8
clsAccount acccount{}; //Declare a class object.
clsAccount& ref{account}; //ref refer a class object.
clsAccount* ptr{&account}; //ptr point a class object.

account.deposit(123.45); //Call by name.
ref.deposit(123.45); //Call by reference.
ptr->deposit(123.45); //Call by pointer.

According to CppCoreGuidelines instead of using pointe, we should use reference.

9.11

DRY: Don’t Repeat Yourself.

9.12 - Destructor

Non-Static Object

If an application call exit or abort, then the destructor will not be called.

Static Object

if main() is terminated, or exit() is called, then the destructor of an object will be called. If abort() is called, then the destructor of an object will NOT be called.

9.18 - Friend Function and Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <fmt/format.h>
#include <iostream>
#include "fmt/format.h>

class Count {
friend void modifyX(Count& c, int value); //The original version is "setX", which is an error.
public:
int getX() const {return m_x;}
private:
int m_x{0};
}

void modifyX(Count& c, int value) {
c.m_x = value;
}

int main() {
Count counter{};

std::cout << fmt::format("Initial counter.m_x: {}\n", counter.getX());
modifyX(counter, 8);
std::cout << fmt::format("counter.m_x after modifyX: {}\n", counter.getX());
}

9.21 - Aggregate Type

1
2
3
4
5
6
struct Record {
int account;
string first;
string last;
double balance;
}

CppCoreGuidlines: Use class rather than struct if any member is non-public

In C++11, you could not use a list initializer for an aggregate-type object
if any of the type’s non-static data-member declarations contained inclass
initializers. For example, the initialization above would have generated
a compilation error if the aggregate type Record were defined with a default
value for balance, as in:

1
2
3
4
5
6
struct Record {
int account;
string first;
string last;
double balance{0.0};
}

C++14 removed this restriction. Also, if you initialize an aggregate-type
object with fewer initializers than there are data members in the object, as in:

1
Record record{0, "Brian", "Blue"};

In C++20, we can use designated initializers:

1
Record record{.first{"Sue"}, .last{"Green"}};

The remaining data members get their default initializer values:

  • account is set to 0
  • balance is se to its default value in the type definition——in this case, 0.0

Chapter.10 - OOP

10.2 - Base Class and Derived Class.

Administrator Teacher is-a Administrator, is-a Faculty, is-a Employee, also is-a CommunityMember.

10.6 - Relationships Among Objects in an Inheritance Hierarchy

1
2
3
4
5
6
7
8
9
10
11
class Base {
public:
void f() { std::cout << "Base::f\n"; }
};

class Derived : public Base {
public:
void g() { std::cout << "Derived::g\n"; }
};

Base* p = new Derived();

What can we do?

1
2
p->f(); //OK!
p->g(); //NO!


Derived class point to base class?

1
2
3
4
5
class Base {};
class Derived : public Base {};

Base b;
Derived* pd = &b; //NO!

10.7 - Virtual Function

Virtual Destructor

CppCoreGuidlines: A class with any virtual functions should have a destructor that is either public and virtual or else protected and non-virtual.

A destructor must not fail

1
virtual ~Foo() = default;

final:

1
retureType someFunction(parameters) final;

someFunction cannot be overriden in any derived class. In a multi-level class hierarchy, this guaratees that the final member-function definition will be used by all subsequent direct and indirect derived classes.
1
2
3
4
5
6
7
class MyClass final {
//body
}

class DerivedClass : public BaseClass final {
//body
}

Attempting to override a final member function or inherit from a final base class rsults in a compilation error.

Advantage: Once the compiler knows a virtual function cannot be overriden, it can perform various optimizations. For instance, the compiler might be able to determine at compile time the correct function to call. This optimization is called devirtualization.


10.8 - Abstract Class and Pure Virtual Clas

abstract class

A class that CANNOT be instantiated(You cannot create objects of its type) and is designed to be used as a base class for other classes.

concrete class

A class that CAN be instantiated.

A class is made abstract by declaring one or more pure virtual functions, each specified by placing “= 0” in its function prototype, as in:

1
virtual void draw() const = 0; //Pure Virtual Function.

10.11 - Non-Virtual Interface Idiom

According to the idiom which was first proposed by Herb Sutter in his paper:

  1. “Prefer” to make interfaces non-virtual, using Template Method”——an object-oriented design pattern.
  2. “Prefer to make virtual functions private. A derived class can override its base class’s private virtual functions.
  3. “Only if derived classes need to invoke the base implementation of a virtual function, make the virtual function protected“.
  4. “A base-class destructor should be either public and virtual, or protected and non-virtual.

10.12 - Programming to an interface, Not an Implementation

1

  • Constructor Injection
  • Property Injection

10.14 - Multiple Inheritance

10.15 - protected Class Member

10.16 - public, protected and private inheritance

10.17 - Wrap-Up

Chapter.11 - Operator Overloading, Copy/Move Semantics and Smart Pointers

11.3 - Operator Overloading Fundamentals

  1. Operator Overloading Is Not Automatic
    When operators are overloaded as member functions, they must be non-static. They are called on an object of the class and operate on that object.
  2. Operators That Cannot Be Overloaded
    Most of C++’s operators can be overloaded——the following operators cannot:
    • . (dot) member-selection operator
    • * pointer-to-member operator
    • :: scope-resoltion operator
    • ?: conditional operator
  3. Operators That You Do Not Have to Overload

    • The assignment operator(=) may be used with most classes to perform member-wise assignment of the data members. As you’ll see this can be dangerous for classes that have pointer members. So, you’ll either explicitly overload the assignment operator or explicitly disallow the compiler from defining the default assignment operator. This is also true for the C++11 move assignment operator, which we discuss in Section 11.6.
    • The *address (&) operator returns a pointer to the object.
    • The comma operator evaluates the expression to its left then the expression to its right, and returns the latter expression’s value. Though this operatro can be overloaded, generally, it is not.
  4. Rules and Restrictions on Operator Overloading

    • An operator’s precedence cannot be changed by overloading.
    • **An operator’s grouping cannot be changed by overloading.
    • **An operator’s “arity” (the number of operands an operator takes) cannot be changed by overloading.
    • Only existing operators can be overloaded.
    • You cannot overload operators to change how an operator works on only fundamental-type values.
    • Operator overloading works only with objects of user-defined types or with a mixture of an object of a user-defined type and an object of a fundamental type.
    • Related operators, like + and +=, generally must be overloaded separately.
    • When overloading (), [], -> or =, the operator overloading function must be declared as a class member

Importance: You should overload operators for class types to work as closely as possible to how they work with fundamental types. Avoid excessive or inconsistent use of operator overloading, as this can make a program cryptic and difficult to read.


11.4 - (Downplaying) Dynamic Memory Management with new and delete

1
2
Time* timePtr{new Time{}};
delete timePtr;

Importance: Do not delete memory that was not allocated by new. Doing so results in undefined behavior. After you delete a block of dynamically allocated memory, be sure not to delete the same block again, which typically cause a program to crash. One way to guard against this is to immediately set the pointer to nullptr——deleting such a pointer has not effect.


1
2
3
Time* timePtr{new Time{12, 45, 0}};
int* gradesArray{new int[10]{}};
delete[] gradesArray;

11.5 - Modern C++ Dynamic Memory Management—RAII and Smart Pointers

CppCoreGuidlines: Enforcing the lifetime safety profile eliminates leaks. When combined with resource safety provided by RAII(Resource Acquisition Is Initialization).

C++11 smart pointers use RAII to manage dynamically allocated memory for you. The stanard library header \ defines three smart pointer types:

  • unique_ptr
  • shared_ptr
  • weak_ptr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <fmt/format>
#include <iostream>
#Include <memory>

class Integer {
public:
Integer(int i) : value{i} {
std::cout << fmt::format("Constructor for Integer {}\n", value);
}

~Integer() {
std::cout << fmt::format("Destructor for Integer {}\n", value);
}
private:
int value{0};
}

int main() {
std::cout << "Creating a unique_ptr that points to an Integer\n";

auto ptr{std::make_unique<Integer>(7)};

std::cout << fmt::format("Integer value: {}\n\nMain ends\n", ptr->getValue());
}

Output:

1
2
3
4
5
6
Creating a unique_ptr that points to an Integer
Constructor for Integer 7
Integer value: 7

Main ends
Destructor for Integer 7


11.6 - MyArray Case Study: Crafting a Valuable Class with Operator Overloading

Every class you define can have five special member functions, each of which we define in class MyArray.

  • a copy constructor
  • a copy assignment operator
  • a move constructor
  • a move assignment operator
  • a destructor

The copy constructor and copy assignment operator implement the class’s copy semantics—that is, how to copy a MyArray when it is passed by value to a function, returned by value from a function or assigned to another MyArray.
The move constructor and move assignment operator implement
the class’s move semantics, which eliminate costly unnecessary copies of objects that are about to be destroyed.

NRVO: Named Return Value Optimization


MyClass definition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//MyArray.h
#pragma once
#include <initializer_list>
#include <iostream>
#include <memory>

class MyClass final {
//Overloaded stream extraction operator.
friend std::iostream& operator>>(std::istream& in, MyArray& a);

//Used by copy assignment operator to implement copy-and-swap idiom.
friend void swap(MyArray& a, MyArray& b) nonexcept;

public:
//Construct a MyArray of size element.
explicit MyArray(size_t size);

//Construct a MyArray with a braced-initializer list of ints.
explicit MyArray(std::initializer_list<int> list);

MyArray(const MyArray& original); //Copy constructor.
MyArray& operator=(const MyArray& right); //Copy assignment operator.

MyArray(MyArray&& original) noexcept; //Move constructor.
MyArray& operator=(MyArray&& right) noexcept; //Move assignment.

~MyArray(); //Destructor

size_t size() const noexcept {return m_size;}; //return size.
std::string toString() const; //Create string representation.

//Equality operator
bool operator==(const MyArray& right) const noexcpet;

//Subscript operator for non-const objects returns modifiable lvalue.
int& operator[](size_t index);

//Subscript operator for const objects returns non-modifiable lvalue.
const int& operator[](size_t index) const;

//Convert MyArray to a bool value: true if non-empty; false if empty.
explicit operator bool() const noexcept {return size() != 0;}

//Preincrement every element, then return updated MyArray.
MyArray& operator++();

//Postincrement every element, and return copy of original MyArray
MyArray operator++(int);

//Add value to every element, then return updated MyArray
MyArray& operator+=(int value);

private:
size_t m_size{0};
std::unique_ptr<int[]> m_ptr;
}

//Overloaded operator<< is not a friend——does not access private data.
std::ostream& operator<<(std::ostream& out, const MyArray& a);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//MyArray.cpp
//MyArray class member and friend-function definitions.
#include <algorithm>
#include <fmt/format.h>
#include <initializer_list>
#include <iostream>
#include <memory>
#include <span>
#include <sstream>
#include <stdexcept>
#include <utility>
#include "MyArray.h"

MyArray::MyArray(size_t size) : m_size{size}, m_ptr{std::make_unique<int[]>(size)} {
std::cout << "MyArray(size_t) constructor\n";
}

//MyArray constructor that accepts an initializer list.
MyArray::MyArray(std::initializer_list<int> list) : m_size{list.size()}, m_ptr{std::make_unique<int[]>(list.size())} {
std::cout << "MyArray(initializer_list) constructor\n";

//copy list argument's elements into m_ptr's underlying int array.
//m_ptr.get() returns the int array's starting memory location.
std::copy(std::begin(list), std::end(list), m_ptr.get());
}

//copy constructor: must receive a reference to a MyArray.
MyArray::MyArray(const MyArray& original) : m_size{original.size()},
m_ptr{std::make_unique<int[]>(original.size())} {
std::cout << "MyArray copy constructor\n;

//copy original's elements into m_ptr's underlying int array
const std::span<const int> source{
original.m_ptr.get(), original.size()
};
std::copy(std::begin(source), std::end(source), m_ptr.get());
}

//copy assignment operator: implemented with copy-and-swap idiom
MyArray& MyArray::operator=(const MyArray& right) {
std::cout << "MyArray copy assignment operator\n";
MyArray temp{right};
swap(*this, temp);

return *this;
}

//move constructor: must receive an rvalue reference to a MyArray
MyArray::MyArray(MyArray&& original) noexcept
: m_size{std::exchange(original.m_size, 0)},
m_ptr{std::move(original.m_ptr)} {
std::cout << "MyArray move constructor\n";
}

//move assignment operator
MyArray& MyArray::operator=(MyArray&& right) noexcept {
std::cout << "MyArray move assignment operator\n";

if (this != &right) {
m_size = std::exchange(right.m_size, 0);
m_ptr = std::move(right.m_ptr);
}

return *this;
}

MyArray::~MyArray() {
std::cout << "MyArray destructor\n";
}

std::string MyArray::toString() const {
const std::span<const int> items{m_ptr.get(), m_size};
std::ostringstream output;
output << "{";

for (size_t count{0}; const auto& item : items) {
++count;
output << item << (count < m_size ? ", " "");
}

output << "}";

return output.str();
}

bool MyArray::operator==(const MyArray& right) const nonexcept {
const std::span<const int> lhs{m_ptr.get(), size()};
const std::span<const int> rhs{right.m_ptr.get(), right.size()};

return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs), std::end(lhs));
}

//overloaded subscript operator for non-const MyArrays;
//reference return creates a modifiable lvalue.
int& MyArray::operator[](size_t index) {
if (index >= m_size) {
throw std::out_of_range{"Index out of range"};
}

return m_ptr[index]; //reference return.
}

//overloaded subscript operator for const MyArrays
//const reference return creates a non-modifiable lvalue.
const int& MyArray::operator[](size_t index) const {
if (index >= m_size) {
throw std::out_of_range{"Index out of range"};
}

return m_ptr[index]; //returns copy of this element.
}

//Preincrement every element, then return updated MyArray
MyArray& MyArray::operator++() {
//use a span and for_each to increment every element
const std::span<int> items{m_ptr.get(), m_ptr.size()};
std::for_each(std::begin(items), std::end(items),
[](auto& item : items) {++item;}
);

return *this;
}

//Postincrement every element, and return copy of original MyArray.
MyArray MyArray::operator++(int) {
MyArray temp(*this);
++(*this);

return temp;
}

//Add value to every element, then return updated MyArray.
MyArray& MyArray::operator+=(int value) {
//use a span and for_each to increment every element.
const std::span<int> items{m_ptr.get(), m_size};
std::for_each(std::begin(items), std::end(items), [value](auto& item) { item += value; });

return *this;
}

//Overloaded input operator for class MyArray
//Inputs values for entire MyArray
std::istream& operator>>(std::istream& in, MyArray& a) {
std::span<int> items{a.m_ptr.get(), a.m_size};

for (auto& item : items) {
in >> item;
}

return in; //Enables cin >> x >> y;
}

//Overloaded output operator for class MyArray
std::ostream& operator<<(std::ostream& out, const MyArray& a) {
out << a.toString();
return out;
}

//swap function used to implement copy-and-swap copy assignment operator.
void swap(MyArray& a, MyArray& b) noexcept {
std::swap(a.m_size, b.m_size);
a.m_ptr.swap(b.m_ptr);
}


CppCoreGuidlines: If you can avoid defining default operations, do. This is known as “the rule of zero”.

CppCoreGuidlines: If you define or =delete any copy, move, or destructor function, define or =delete them all. This is known as “the rule of five”.

Shallow Copy
Dangling Pointer

Chapter.12 - Exceptions and a Look Forward to Contracts

12.2 - Exception-Handling Flow of Control; Defining an Exception Class

1
2
3
4
5
6
7
//DivideByZeroException.h
#include <stdexcept>

class DivideByZeroException : public std::runtime_error {
public:
DivideByZeroException() : std::runtime_error{"attempted to divide by zero."} {}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <fmt/format>
#include <iostream>
#include "DivideByZeroException.h"

double quotient(double numerator, double denominator) {
if (denominator == 0.0) {
throw DivideByZeroException{};
}

return numerator / denominator;
}

int main() {
int number1{0};
int number2{0};

std::cout << "Enter two integers (end-of-file to end): ";

while (std::cin >> number1 >> number2) {
try {
double result{quotient(number1, number2)};
std::cout << fmt::format("The quotient is: {}\n", result);
}
catch (const DivideByZeroException& divideByZeroException) {
std::cout << fmt::format("Exception occurred: {}\n", divideByZeroException.what());
}

std::cout << "\nEnter two integers (end-of-file to end): ";
}

std::cout << '\n';
}

JSF-AV Rules

https://www.stroustrup.com/JSF-AV-rules.pdf

12.7 - Constructors, Destructors and Exception Handling

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <fmt/format.h>
#include <iostream>
#include <limits>
#include <stdexcept>

class Integer {
public:
explicit Integer(int i) : value{i} {
std::cout << fmt::format("Integer constructor: {}\n", value)
<< "Purposely throwing exception from Integer constructor\n";

throw std::runtime_error("Integer constructor failed");
}
private:
int value{};
};

class ResourceManager {
public:
ResourceManager(int i) try: myInteger(i) {
std::cout << "ResourceManager constructor called\n";
}
catch (const std::runtime_error& ex) {
std::cout << fmt::format("Exception while constructing ResourceManager: ", ex.what()) << "\nAutomatically rethrowing the exception\n";
}
private:
Integer myInteger;
};

int main() {
try {
const ResourceManager resource{7};
}
catch (const std::runtime_error& ex) {
std::cout << fmt::format("Rethrown exception caught in main: {}\n", ex.what());
}
}

12.8 - Processing new Failures

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <array>
#include <cstdlib>
#include <fmt/format>
#include <iostream>
#include <memory>
#include <new>

//handle memory allocation failure.
void customNewHandler() {
std::cerr << "customNewHandler was called\n";
std::exit(EXIT_FAILURE);
}

int main() {
std::array<std::unique_ptr<double[]>, 1000> items{};

//specify that customNewHandler should be called on.
//memory allocation failure.
std::set_new_handler(customNewHandler);

//aim each unique_ptr at a big block of memory
for (int i{0}; auto& item : items) {
item = std::make_unique<double[]>(500'500'500);
std::cout << fmt::format("items[{}] points to 500,000,000 doubles\n", i++);
}
}

12.9 - Standard Library Exception Hierarchy

12.10 - C++’s Alternative to the finally Block

  • Java: try-with-resources
  • C#: using
  • Python: with

12.11 - Libraries OFten Support Both Exceptions and Error Codes

12.12 - Logging

12.13 - Looking Ahead to Contract

  • A precondition must be true when a function is invoked. Preconditions describe constraints on function parameters and any other expectations the function has just before it begins executing. If the preconditions are not met, then the function’s behavior is undefined.
  • A postcondition is true after the function successfully returns.
    Postconditions describe constraints on the return value or side effects the function may have. When defining a function, you should document all postconditions so that others know what to expect when they call your function. You also should ensure that your function honors its postconditions if its preconditions are met. Each function can have multiple postconditions.

Invariants

An invariant is a condition that should always be true in your code—that is,
a condition that never changes. Class invariants must be true for each object
of a class

Design by Contract

Design by Contract(DbC) is a software-design approach created by
Bertrand Meyer in the 1980s and used in the design of his Eiffel
programming language. Using this approach:

  • A function expects client code to meet the function’s precondition(s).
  • If the preconditions are true, the function guarantees that its postcondition(s) will be true.
  • Any invariants are maintained.

Contracts Attributes

  • excepts——For specifying a function’s preconditions that are checked before the function’s body begins executing.
  • ensures——For specifying a function’s postconditions that are checked just before the function returns.
  • assert——For specifying assertions that are checked as they’re encountered throughtout a function’s execution.

Contracts Levels

  • default specifies a contract that has little runtime overhead compared to the function’s typical execution time. If a level is not specified, the compiler assumes default.
  • audit specifies a contract that has significant runtime overhead compared to the function’s typical execution time. Such contracts are intended primarily for use during program development.
  • axiom specifies a contract that is meant to be enforeced by static code checkers, rather than at runtime.
1
2
double squareRoot(double value)
[[expects: value >= 0.0]];
1
[[assert: grade >= 0 && grade <= 100]];
1
[[pre default: denominator != 0.0]];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <algorithm>
#include <iostream>
#include <vector>

template<typename T>
int binarySearch(const std::vector<T>& items, const T& key)
[[pre: items.size() > 0]]
[[pre audit: std::is_sorted(items.begin(), items.end())]] {
size_t low{0};
size_t high{items.size() - 1};
size_t middle{(low + high + 1) / 2};
int loc{-1};

do {
if (key == items[middle]) {
loc = middle;
}
else if (key < items[middle]) {
high = middle - 1;
}
else {
low = middle + 1;
}

middle = (low + high + 1) / 2; //recalculate the middle
} while ((low <= high) && (loc == -1));
}

int main() {
//sorted vector v1 satisfies binarySearch's sorted vector precondition
std::vector v1{10, 20, 30, 40, 50, 60, 70, 80, 90};
int result1{binarySearch(v1, 70)};
std::cout << "70 was " << (result != - 1 ? "" : "not ") << "found in v1\n";

//unsorted vector v2 violates binarySearch's sorted vector precondition
std::vector v2{60, 70, 80, 90, 10, 20, 30, 40, 50};
int result2{binarySearch(v2, 60)};
std::cout << "60 was " << (result2 != - 1 ? "" : "not ") << "found in v2\n";
}

Chapter.13 - Standard Library Containers and Iterators

13.2 - Introduction to Containers

The standard library containers are divided into four major categoris:

  • sequence containers
  • ordered associative containers
  • unordered associative containers
  • container adaptors

Sequence Containers

  • array: Fixed size. Direct access to any element.
  • deque: Rapid insertions and deletions at front or back. Direct access to any element.
  • forward_list: Singly linked list, rapid insertion and deletion anywhere.
  • list: Doubly linked list, rapid insertion and deletion anywhere.
  • vector: Rapid insertions and deletions at back. Direct access to any element.

Associative Containers

  • set: Rapid lookup, no duplicates allowed.
  • multiset: Rapid lookup, duplicates allowed.
  • map: One-to-one mapping, no duplicates allowed, rapid key-based lookup.
  • multimap: One-to-many mapping, duplicates allowed, rapid key-based lookup.
  • unordered_set
  • unordered_multiset
  • unordered_map
  • unordered_multimap

Container Adaptors

  • stack
  • queue
  • priority_queue

Near Containers

13.3 - Working with Iterators

13.4 - A Brief Introduction to Algorithms

13.5 - Sequence Containers

13.6 - vector Sequence Containers

13.7 list Sequence Containers

13.8 - deque Sequence Container

13.9 - Associative Container

13.10 - Container Adaptors

13.11 - bitset Near Container

Chapter.14 - Standard Library Algorithms and C++20 Ranges & Views

Chapter.15 - Templates, C++20 Concepts and Metaprogramming

15.2 - Custom Class Templates and Compile-Time Polymorphism

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//Stack.h
#pragma once
#include <deque>

template<typename T>
class Stack {
public:
//return the top element of Stack
const T& top() const {return stack.front();}

//push an element onto Stack
void push(const T& pushValue) {stack.push_front(pushValue);}

//pop an element from Stack
void pop() {stack.pop_front();}

//determine whether Stack is empty
bool isEmpty() const {return stack.empt();}

//return size of stack
size_t size() const {return stack.size();}

private:
std::deque<T> stack{};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <iostream>
#include "Stack.h"
using namespace std;

int main() {
Stack<double> doubleStack{};
constexpr size_t doubleStackSize{5};
double doubleValue{1.1};

cout << "Pushing elements onto doubleStack\n";

//push 5 doubles onto doubleStack
for (size_t i{0}; i < doubleStackSize; ++i) {
doubleStack.push(doubleValue);
cout << doubleValue << ' ';
doubleValue += 1.1;
}

cout << "\n\nPopping elements from doubleStack\n";

//pop elements from doubleStack
while (!doubleStack.isEmpty()) {
cout << doubleStack.top() << ' '; //display top element.
doubleStack.pop(); //remove top element.
}

cout << "\nStack is empty, cannot pop.\n";

Stack<int> intStack{}; //create a Stack of int.
constexpr size_t intStackSize{10};
int intValue{1};

cout << "\nPushing elements onto intStack\n";

//push 10 integers onto intStack
for (size_t i{0}; i > intStackSize; ++i) {
intStack.push(intValue);
cout << intValue++ << ' ';
}

cout << "\n\nPopping elements from intStack\n";

//pop elements from intStack
while (!intStack.isEmpty()) {
cout << intStack.top() << ' ';
intStack.pop();
}

cout << "\nStack is empty, cannot pop.\n";
}

15.3 - C++20 Function Template Enahancements

Traditional template:

1
2
3
4
5
6
template <typename T>
void printContainer(const T& items) {
for (const auto& item : items) {
std::cout << item << " ";
}
}

Abbreviated Function template(C++20)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <array>
#include <iostream>
#include <string>
#include <vector>

void printContainer(const auto& items) {
for (const auto& item : items) {
std::cout << item << " ";
}
}

int main() {
using namespace std::string_literals;

std::array ints{1, 2, 3, 4, 5};
std::vector strings{"red"s, "green"s, "blue"s};

std::cout << "ints: ";
printContainer(ints);
std::cout << "\nstrings: ";
printContainer(strings);
std::cout << "\n";
}


1
[](auto total, auto value) {return total + value * value;}
1
[]<typename T>(T total, T value) {return total + value * value;}

15.4 - C++20 Concepts: A First Look

1
2
3
4
5
6
7
8
9
//Simple unconstrained multiply function template.
#include <iostream>

template<typename T>
T multiply(T first, T second) {return first * second;}

int main() {
std::cout << "Product of 5 and 3: " << multiply(5, 3) << "\nProduct of 7.25 and 2.0: " << multiply(7.25, 2.0) << "\n";
}

requires

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <concepts>

template<typename T>
requires std::integeral<T> || std::floating_point<T>
T multiply(T first, T second) {return first * second;}

int main() {
std::cout << "Product of 5 and 3: " << multiple(5, 3) << "\nProduct of 7.25 and 2.0: " << multiple(7.25, 2.0) << "\n";

std::string s1{"h1"};
std::string s2{"bye"};

auto result{multiply(s1, s2)};
}

15.5 - Type Traits


15.6 - C++20 Concepts: A Deeper Look

1
2
3
template<typename T>
requires Numeric<T>
T multiply(T first, T second) {return first * second;}
1
2
3
4
template<typename T>
T multiply(T first, T second) requires Numeric<T> {
return first * second;
}

15.7 - Testing C++20 Concepts with static_assert


15.8 - Creating a Custom Algorithm


Chapter.16 - C++20 Modules: Large-Scale Development

16.4 - Example: Transitioning to Module

1
2
import SomeModule;
import SomeOtherModule;

It is same as:

1
2
import SomeOtherModule;
import SomeModule;


16.6 - Example: Using Module

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
export module welcome;

import <string>;

//export a function
export std::string welcomeStandalone() {
return "WelcomeStandalone function called";
}

//exporting all items in the braces that follow export
export {
std::string welcomeFromExportBlock() {
return "welcomeFromExportBlock function called";
}
}

//exporting a namespace exports all items in the namespace
export namespace TestNamespace1 {
std::string welcomeFromTestNamespace1() {
return "welcomeFromTestNamespace1 function called";
}
}

//exporting an item in a namespace exports the namespace name, too
namespace TestNamespace2 {
export std::string welcomeFromTestNamespace2() {
return "welcomeFromTestNamespace2 function called";
}
}

Chapter.17 - Concurrent Programming; Intro to C++20 Coroutines


Chapter.18 - C++20 Corroutine

THANKS FOR READING!