[Book] C++ 20 for Programmers - An Objects-Natural Appraoch
Last Update:
Word Count:
Read Time:
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 | |
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 | |
| 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 | |
8.18 - Raw String Literal
1 | |
1 | |
Chapter.9
9.9 - Access the Member of class
1 | |
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 | |
9.21 - Aggregate Type
1 | |
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
6struct 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 | |
What can we do?1
2p->f(); //OK!
p->g(); //NO!
Derived class point to base class?1
2
3
4
5class 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 | |
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
7class 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:
- “Prefer” to make interfaces non-virtual, using Template Method”——an object-oriented design pattern.
- “Prefer to make virtual functions private. A derived class can override its base class’s private virtual functions.
- “Only if derived classes need to invoke the base implementation of a virtual function, make the virtual function protected“.
- “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
- 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. - 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
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.
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 | |
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 | |
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 \
- unique_ptr
- shared_ptr
- weak_ptr
1 | |
Output:1
2
3
4
5
6Creating 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 definition1
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 | |
1 | |
JSF-AV Rules
12.7 - Constructors, Destructors and Exception Handling
1 | |
12.8 - Processing new Failures
1 | |
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 | |
1 | |
1 | |
1 | |
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 | |
1 | |
15.3 - C++20 Function Template Enahancements
Traditional template:1
2
3
4
5
6template <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 | |
1 | |
15.4 - C++20 Concepts: A First Look
1 | |
requires
1 | |
15.5 - Type Traits
15.6 - C++20 Concepts: A Deeper Look
1 | |
1 | |
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 | |
It is same as:1
2import SomeOtherModule;
import SomeModule;
16.6 - Example: Using Module
1 | |
Chapter.17 - Concurrent Programming; Intro to C++20 Coroutines
Chapter.18 - C++20 Corroutine
THANKS FOR READING!