about
10/17/2022
C++ Story Survey
Chapter #2 - C++ Survey
data, operations, classes, templates, libraries
2.0 Survey Prologue
2.1 Data Types
void, bool, nullptr_t, integral, char, and floating types
- void - only type with no values
- bool - values true and false
- std::nullptr_t - type of the nullptr literal
-
integral types:
int with qualifiers: short, long, long long, unsigned, const, volatile
std::size_t, std::size_type -
character types:
char, wchar_t with qualifiers: signed, unsigned, const, volatile
unicode characters: char16_t, char32_t, char8_t (C++20), with qualifiers: const, volatile -
floating point types:
float, double with qualifiers: long (double only), const, volatile
arrays
pointers
A pointer is a reference to the memory location of a variable, to which it is bound.
X x;
X* pX = &x;
[& on right of assignment is an address]
pX is a pointer variable containing the address of x
++pX increments address stored in pX by sizeof(X)
Retrieve value referenced by pX using dereference operator, *pX , which returns
the value of x .
X* pX = &x;
Example: Reading command line arguments
C++ references
STL sequential and associative containers, container adapters
contiguous memory storage, can be indexed | |
forward_list, list | nodes allocated on heap, cannot be indexed |
key only storage using balanced binary tree or hash table | |
key-value storage using balanced binary tree or hash table |
sequential containers accessible only from end(s) | |
constant time lookup of largest element, logarithmic insertion and extraction |
STL-Containers.html
Example: Sum elements in container using std::vector and std::for_each
Special containers, streams, and other types
pair | contains two elements which may have distinct types |
tuple | contains finite number of elements with distinct types |
initializer_list | contains sequence of elements, all of the same type normally filled with initialization list, e.g., { 1, 2, 3, ... } |
any | holds value of any type, provides std::any_cast for retrieval |
optional | used to return values or signal failure to return |
variant | similar to any, but only holds values from a specified set of types |
istream | sends sequence of values of fundamental types to console using insertion operator<<, which may be overloaded for user-defined types. |
ostream | retrieves sequence of values of fundamental types from keyboard using extraction operator>>, which may be overloaded for user-defined types. |
ifstream | retrieves sequence of values of fundamental types from attached file using extraction operator<<, which may be overloaded for user-defined types. |
ofstream | retrieves sequence of values of fundamental types from attached file using extraction operator>>, which may be overloaded for user-defined types. |
istringstream | retrieves sequence of values of fundamental types from attached in-memory string using extraction operator<&lot;, which may be overloaded for user-defined types. |
ostringstream | retrieves sequence of values of fundamental types from attached in-memory string using extraction operator>>, which may be overloaded for user-defined types. |
unique_ptr<T> | Construction allocates instance t ε T, destruction deallocates t. Assignment moves ownership. |
shared_ptr<T> | Reference counted smart pointer, assignment adds counted reference. |
exception | When code error occurs exception instance is "thrown", then handled by catch clause associated with enclosing try block. |
chrono | Class for managing time durations and dates. |
Example: Write file using file stream and std::optional
Type instance sizes
2.1.1 User Defined Types
User defined types: enums, structs, classes, and type aliasesscoped enum: enum class E { ... };
struct S { ... };
class C { ... };
type alias: using AliasName = Sometype;
Testing types at compile-time and run-time
void display(const T& t) {
if constexpr (std::is_fundamental<T>::value) {
// do display operations consistent with fundamental types
// std::is_fundamental<T> is a std::type-trait
}
// do something knowing that x1 and x2 have the same types
// the typeid operator returns a type_info instance. that's what we are comparing.
}
2.1.2 Definitions and Declarations
type declaration
type definition
Life-time of this storage extends from the point of declaration until the thread
of execution leaves the current scope.
Life-time of this allocation is the life time of the program after the program's
process has been created until the process begins termination.
Life-time of this storage extends from the point of declaration until the thread
of execution leaves the current scope.
Life-time of the memory
reservation for t extends from first use to the end of program
execution.
Life-time for this storage extends from the time the new operator is invoked
until delete operator is called on pX .
We will provide examples of, and cover many details about, C++ data types in Chapter #3 - Data.
2.2 Operations
program scopes
-
namespace N { ... } -
class C { ... }; -
struct S { ... } -
enum class E { ... } -
void f(int i) { ... } -
try { ... } catch(execption& ex) { ... } -
for(startExpression; endExpression; incrementExpression) { ... } -
for(T t : container) { ... } T may be replaced byauto -
while(predicate) { ... } -
do { ... } while(predicate); -
if(predicate) { ... } else { ... }
dereference and address of: * &
member access operations: . ->
pre increment and decrement: ++i --i
++i increments i and returns value 1
--i decrements i and returns value 0
post increment and decrement: i++ i--
i++ increments value to 1 and returns prior value 0
i-- decrements value to 0 and returns prior value 1
logical operators: == != < <= >= > && || !
two of these logical operations: struct X {
int i; double d;
bool operator==(const X& x) {
return i == x.i && d == x.d;
}
bool operator!=(const X& x) {
return i != x.i || d != x.d;
}
}; X x1{ 1, 1.5 }, x2{ 1, 1.5 }; x3{ 2, -0.5 }
x1 == x2; x1 != x3; Both statements above are true.
index operator: a[]
arithmetic operations: + - * /
loops: for, while, do
Any, or all, of the three loop conditions may be omitted, provided
that omitted conditions are defined. If no termination condition is provided,
the loop requires a conditional break statement to terminate.
Loop operations elided
}
for(auto item : collection) {
Collection must provide an iterator and methods begin() and end()
which return iterators referring to the first element and one past
the last of its elements.
Loop operations elided
}
while(predicate) {
Loop operations will not be executed if predicate is false on entry.
Loop will continue until operations in scope of the loop
make its predicate false.
Loop operations elided
}
do {
Ensures that loop operations are executed at least once.
Loop will continue until operations in scope of the loop
make its predicate false.
Loop operations elided
} while(predicate);
selection: if-else, ternary operator, switch
Operations to execute when predicate is true go here.
}else {
Operations to execute when predicate is false go here.
This else clause is optional.
}
The ternary operator:
predicate ? e1 : e2
returns the value of expression e1 if predicate
evaluates to true , otherwise it returns the value of expression
e2 .
The switch operation enables execution of code specific to some specified case.
switch selectors, iSelect , and switch cases, iSelect1, iSelect2, ...
belong to a list, IS, of integral selectors, e.g., distinct integers or values of an enumeration:
iSelect, iSelect1, iSelect2, ... ε IS
switch(iSelect) {
case iSelect1:
case iSelect2:
----
default:
break;
Operations for iSelect1 case go here.
break;case iSelect2:
Operations for iSelect2 case go here.
break;----
default:
Operations when iSelect does not match any declared
iSelect[n] case go here.
}
function call operator: f()
{
std::cout << "\n " << str;
} f("hello Syracuse"); displays message on console
method call operator: operator()
void operator()(const std::string& s) {
std::cout << "\n " << s;
}
}; X x;
x("hello");
x.operator()("hello"); The last two statements are equivalent.
Lambda: [/* capture */](/* args */) { /* code block */ };
2.3 Classes and Class Relationships
basic class
Class Person declares personal data contained by each instance. These "stats" contain
name, occupation, and age,
held in a std::tuple .
Instances of the Person class can be copied and assigned because its only data member,
personStats is a std::tuple.
The std::tuple and its elements all have correct copy, assignment, and destruction semantics.
Compiler generated methods for copying, assignment, and destruction do those operations
on each of a class's base classes and composed member data items. In this case, those
generated operations simply use the std::tuple's copy, assignment, and destruction operations.
class Person { public: using Name = std::string; using Occupation = std::string; using Age = int; using Stats = std::tuple<Name, Occupation, Age>; Person(); Person(const Stats& sts); Stats stats() const; void stats(const Stats& sts); bool isValid(); Name name() const; Occupation occupation() const; void occupation(const Occupation& occup); Age age() const; void age(const Age& ag); private: Stats personStats; };
Note that Person defines four type aliases with names that make the class's code
readable,
easer to test, and easer to use.
It provides getter and setter methods for attributes
occupation and age that should be changeable, and only a getter for the name attribute
which should not.
References:
CppStory Repository
function overloading
Consider the two Person constructors, from the previous "basic class" details:
Person(const Stats& sts);
- Return types play no role in function overloading.
- Actually, the name consists of a tokenized sequence that identifies the function name and argument types in a compact format.
function overloading example
/*---- find first and last elements of collection, may throw ----*/ using PD = std::pair<double, double> PD firstAndLast(double dArr[], size_t N) { if (N < 1) throw std::exception("no contents in array"); return PD{ dArr[0], dArr[N - 1] }; } using PI = std::pair<int, int> PI firstAndLast(const std::vector<int>& vecInt) { if (vecInt.size() < 1) throw std::exception("no contents in vector"); return PI{ vecInt[0], vecInt[vecInt.size() - 1] }; } /*---- find first and last elements of collection, using optional ----*/ std::optional<PD> firstAndLastOpt(double dArr[], size_t N) { std::optional<PD> opt; if(N > 0) opt = std::pair{ dArr[0], dArr[N - 1] }; return opt; } std::optional<PI> firstAndLastOpt(const std::vector<int>& vecInt) { std::optional<PI> opt; if (vecInt.size() > 0) opt = std::pair{ vecInt[0], vecInt[vecInt.size() - 1] }; return opt; } /*---- demonstrate first and last ----*/ int main() { std::cout << "\n Demonstrating Function Overloading"; std::cout << "\n ====================================\n"; double dArr[]{ 1.5, -0.5, 3.0 }; std::vector<int> vecInt{ 1,2,3,4,5 }; try { auto [firstd1, lastd1] = firstAndLast(dArr, 3); std::cout << "\n first = " << firstd1 << ", last = " << lastd1; auto [firsti1, lasti1] = firstAndLast(vecInt); std::cout << "\n first = " << firsti1 << ", last = " << lasti1; } catch (std::exception & ex) { std::cout << "\n " << ex.what() << std::endl; } std::optional<PD> optd = firstAndLastOpt(dArr, 3); if (optd.has_value()) { auto [firstd2, lastd2] = optd.value(); std::cout << "\n first = " << firstd2 << ", last = " << lastd2; } else { std::cout << "\n array query failed"; } std::optional>PI< opti = firstAndLastOpt(vecInt); if (opti.has_value()) { auto [firsti2, lasti2] = opti.value(); std::cout << "\n first = " << firsti2 << ", last = " << lasti2; } else { std::cout << "\n vector query failed"; } std::cout << "\n\n"; }
class relationships
- Inheritance: a specialization of a base class by another derived class.
- Composition: a permanent relationship between the composer and composed.
- Aggregation: a temporary relationship between the aggregator and aggregated.
- Using: a non-owning relationship. The used is made available to the user by passing as a reference argument in a class method.
- Friendship: a relationship granted by a class to one specific friend that allows the friend access to the class's private members. Friendship weakens encapsulation and so is used only when necessary, e.g., rarely.
Fig 1. Person Class Hierarchy
- Interfaces:
IPerson andISW-Eng - An abstract class:
SW-Eng -
Concrete classes:
Person, Dev, TeamLead, ProjMgr, Project, Baseline, Documents, andBudget
function overriding
function overriding example
We will discuss the details of techniques used in this example in Chapter 5.
2.4 Templates and Specialization
template functions
T max(T t1, T t2) {
return t1 > t2 ? t1 : t2;
}
overloading template functions
T max(T t1, T t2) {
return t1 > t2 ? t1 : t2;
}
template <>
pStr max(pStr s1, pStr s2) {
return ((strcmp(s1,s2)>0) ? S1 : s2);
}
template classes
class stack {
public:
void push(T t);
T pop();
T top();
std::size_type size();
private:
// data members elided
}; template <typename T>
void stack<T>::push(T t) {
// details elided
} // other member function definitions elided
stack<std::pair<std::string, int>> prStk;
stack<Widget> WdgStk;
template class specialization
class stack {
public:
stack();
stack(const stack<T>& stk);
stack<T>& operator=(const stack<T>& stk);
void push(T t);
T pop();
T top();
std::size_type size();
private:
// data members elided
}; template <typename T>
void stack<T>::push(T t) {
// details elided
} // other member function definitions elided
class stack<Widget> {
public:
stack();
stack(const stack<T>& stk) = delete;
stack<T>& operator=(const stack<T>& stk) = delete;
void push(Widget w);
Widget pop();
Widget top();
std::size_type size();
private:
// elided data members may be different from the generic class
}; template <typename T>
void stack<T>::push(T t) {
// elided details may be different from the generic class
} // other member function definitions elided
// and exhibits correct operation
- We will discuss, in Chapter 5., how incomplete designs happen and how to ensure your designs are complete.
2.5 Libraries
standard C++ libraries
Only the organization has been changed (slightly) and a few of the more obscure libraries omitted. I've annotated the material and emphasized those libraries I use frequently. You will see most of them in examples throughout this story.
-
Language Support libraries
- <initializer_list> - supports uniform initialization for user-defined types, examples in Chapter #3 - Data.
- <type_traits> - used for template metadata programming, examples in Chapter 3.
- <limits> - Numeric limits
- <cstdlib> - Managing OS Processes and Signals
-
General Utilities libraries
- <memory> - Smart pointers and allocators, examples in Chapter 4
- <chrono> - Time durations, times, and dates
-
Function objects and qualilfiers:
<functional> - function, mem_fn, bind, invoke, ref<utility> - move, forward, pair, tuple, examples in Chapters 4 and 5<tuple> - examples in Chapters 2 and 3
- <charconv> - to_chars, from_chars, chars_format
- <optional> - return instance or empty
- <any> - holds instance of almost any type, example in Chapter 3
- <variant> - holds instances of any specified set of types
- <string> - std::basic_string --> std::string, std::wstring
-
Containers libraries:
<array>, <deque>, <list>, <map>, <multimap> <multiset> <queue>, <set>, <singleList>, <stack>, <string>, <unordered_map>, <unordered_multimap>, <unordered_multiset>, <unordered_set>, <vector> -
<algorithm> - Algorithms library
Non-modifying sequence operations: all_of, any_of, none_of, for_each, for_each_n, count, count_if, mismatch, find, find_if, find_if_not, find_end, find_first_of, adjacent_find, search, search_nModifying sequence operations:copy, copy_if, copy_n, copy_backward, move, move_backward, fill, fill_n, transform, generate, generate_n, remove, remove_if, remove_copy, remove_copy_if, replace, replace_if, replace_copy, replace_copy_if, swap, swap_ranges, iter_swap, reverse, reverse_copy, rotate, shift_left, shift_right, random_shuffle, shuffle, sample, unique, unique_copyPartitioning operations:is_partitioned, partition, partition_copy, stable_partition, partition_pointSorting operations:is_sorted, is_sorted_until, sort, partial_sort, stable_sort, nth_elementBinary search on sorted ranges:lower_bound, upper_bound, binary_search, equal_rangeOther operations on sorted ranges:merge, inplace_mergeSet operations on sorted ranges:includes, set_differences, set_intersection, set_symmetric_distance, set_unionHeap operations:is_heap, is_heap_until, make_heap, push_heap, pop_heap, sort_heapMin/Max operations:max, max_element, min, min_element, minmax, minmax_element, clampComparison operations:equal, lexicographical_compare, lexicographical_compare_three_wayPermutation operations:is_permutation, next_permutation, prev_permutationNumeric operations:iota, accumulate, inner_product, adjacent_difference, partial_sum, reduce, exclusive_scan, inclusive_scan, transform_reduce, transform_exclusive_scan, transform_inclusive_scan -
Numerics libraries
- <cstdlib>, <cmath> - Common math functions
- <cmath> - Special math functions
- <numeric>, <cmath> - Numeric algorithms
- <random>, <cstdlib> - Pseudo-random number generators
- <cfenv> - Floating-point environment
- <complex> - Complex numbers, operations
-
Input/Output libraries:
-
Terminal I/O:
<ios>, <streambuf>, <ostream>, <istream>, <iostream>
-
File I/O:
<fstream>
-
String I/O:
<sstream>
-
Synchronized I/O:
<syncstream>
-
I/O manipulators:
<iomanip>
-
Terminal I/O:
- <regex> - Regular Expressions library
-
Thread Support libraries:
C++17:<thread>, <mutex>, <shared_mutex>, <condition_variable>, <future>, <atomic>C++20:<semiphore>, <latch>, <stop_token>
- <filesystem> - Filesystem library
-
Error Handling libraries:
<exception>, <stdexcept>, <cerrno>, <cassert> <system_error>
-
Other libraries
- Localizations
- Iterators
- Concepts (C++20)
- Named Requirements (C++20)
- Ranges (C++20)
Example: Display container contents using std::vector, std::for_each, and lambda
with output:std::vector<int> test{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }; size_t N = 4; auto fold = [N](auto t) { static size_t count = UINT_MAX - 1; if (++count > N) { count = 1; std::cout << "\n "; } std::cout << t << " "; }; std::for_each(test.begin(), test.end(), fold);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
- Network programming and inter-process communication
- Processing XML or JSON data formats
- Building Graphical User Interfaces
custom libraries
Library | Description |
---|---|
FileSystem | Provides interfaces used by many of the applications in code repositories in this site. It was developed using Windows and Linux platform APIs. I plan to turn this into a wrapper for the std::filesystem which provides a different set of interfaces, incompatible with the existing applications. FileSystem.html |
XmlDocument |
A fairly complete processing library for reading, parsing, building,
and writing XML to and from strings and files.
|
CppCommWithFileXfer |
Supports asynchronous message-passing communication between multiple
endpoints, using the Sockets library, below.
|
Sockets |
A sockets class hierarchy that handles IP4 and IP6 protocols
for stream-based sockets. The library has versions for both Windows
and Linux.
|
CppParser |
A rule based parser suitable for analyzing C, C++, C#, and Java.
|
2.6 Survey Epilogue
2.7 Programming Exercises
- Write code that saves an array of strings where the size of the array is specified at run-time. Show how to access stored items and how to deallocate the storage.
-
Write a lambda that accepts a std::string message and displays it on the console with a second
line composed of '-' characters.
If the lambda prepends the message with a newline, indents it two spaces, and makes the underline string two characters longer, with a one character indent, the result creates a nice title. Can you create the lambda so it also accepts an underline character which defaults to '-'? - Develop a class that accepts an initializer_list of strings when constructed and save the elements of the list in a std::vector. Write a member function that adds an additional string to the list. Demonstrate this class in a main() where you supply a list of your friends. Then add two additional new friends.
- Generalize the friends class to accept a list of std::tuples where the tuples provide a bit more information about your friends. Can you make this work for types other than std::tuple, perhaps a struct with the same information. The intent here is that, after the first change, you can use more than one data type without changing your friends class.
2.8 References
cplusplus.com/reference
C/C++ language and standard libraries reference - MSDN
cpppatterns.com
Posts on Fluent C++
C++ Idioms
C++ weekly videos
riptutorial - documentation provided by StackOverFlow
mycplus.com tutorials
cppnow 2018
Declarative Style in C++