Double Dispatch¶
Not a good idea to define virtual operator overloads
virtual MathObject* operator+(const MathObject& b) const;C = *A + *B
- First, inconvenient: have to work with pointers, rather than values (or a mix of pointers and references to values...)
- Second, the operation performed is determined only by the type of the first argument (
*this)- ... and no dynamic polymorphism at all if defined outside the class (e.g.
operator<<)
- ... and no dynamic polymorphism at all if defined outside the class (e.g.
Double dispatch addresses the second point:
- The operation performed is determined by the types of both arguments
Visitor Pattern¶
- Double dispatch can be achieved:
- One virtual function, chosen according to the type of
*this, (first argument) delegates to another virtual function called on the second argument of the “operation.”
- One virtual function, chosen according to the type of
- Used by the classical visitor pattern
- What is performed by the visit operation depends on the type of the object visited and the type of the visitor.

Copying an Object into a Member¶
- How to copy a (large) object into a class member?
- Accept as const lvalue reference parameter
- Add an overload for rvalue reference parameter (optional, for performance)
- Have a template method (pass by universal reference)
- Pass by value (and then moving)!
- Cost of it:
- First: argument is copied/moved
- Then: the parameter is moved
- In total: copy+move for lvalues and move+move for rvalues
- When to doo:
- If cheap to move (e.g. std::array - not cheap to move)
- If always copied (the first copy/move is done before the call - should not be wasted)
- See solution to exercise 5.2 (constructors of class Simple)
Returning from Functions/Methods¶
- Possible return types of functions?
- Value:
Type f()- Standard/preferred.
- Use out parameter (
f(Type& out)) if the Type is large and expensive to move and copy elision/(N)RVO does not help or you want to reuse the same object (seestd::getline(std::istream& , std::string&))
- Pointer:
Type* f()- Only to indicate position in a long-lived object.
- Never as a memory resource handle.
- Danger of a dangling pointer.
- (const) Lvalue reference:
const Type& f(…)- If the function selects among alternatives:
Type& f(Type& op1, Type& op2) - As a return type of a getter/accessor method in a class (or should it return by value?)
- If the function selects among alternatives:
- Rvalue reference: almost never
- Smart pointer:
std::unique_ptr f()- Return ownership of a newly created object on a heap
- Value:
- Returning several values
- The code is more intuitive if
std::pair,std::tuple, or your own structure is returned. - Use structured bindings on the caller side:
- The code is more intuitive if
- When returning a special null/invalid/void is an option:
- Use:
std::optional<Type> f() - Can have
std::nulloptvalue - It stores the value directly inside
- Use:
STL¶
std::string and std::string_view¶
std::stringwraps good oldchar*:- Provides ownership
- Rich manipulation API
- But could be expensive if we need to construct many temporary strings (just for read)
- Example: stringviews.cpp
std::string_viewgives a lightweight, non-owning, read-only view into a string: just a pointer and a size- Rich interface
- Passed by value
- Can be converted from
std::strings and C strings
- Caution:
- Returning an
std::string_view- similar dangers to returning a raw pointer! - As a constructor parameter to initialize a field of an object? - No!
- Returning an
Types of Data Objects¶
- Objects with value semantics (deep copy)
- On the heap (cheap to move) e.g.
std::vector, longstd::string - In-place (move=copy) e.g.
std::array,std::stringwith SSO
- On the heap (cheap to move) e.g.
- "Pointer-like" objects (shallow copy):
std::string_view,std::initializer_list- Small and passed by value
- "Handles" to heap allocated memory (shallow/no copy): smart pointers
- Own the object pointed to
Iterators¶
Iterators are generalized pointers
- The main role is to connect algorithms and containers
- Most important operators:
- Access to the current element:
*and-> - Go to the next element:
++ - Equality and inequality:
==and!=
- Access to the current element:
- The start of a sequence
s:s.begin() - The end of a sequence
s:s.end()- Designates a non-existing one past the last element
For range-for loop, the compiler automatically looks for iterators vd.begin() and vd.end(), or begin(vd) and end(vd).
-
Defined in multiple versions in
<iterator> -
One of very few places where language depends on the standard library!
Categories of Iterators¶
C++ iterators are classified in a number of different dimensions:
- Const and non-const iterators
- Use constant iterators, whenever possible
- Reverse and forward iterators
- For a reverse iterator,
++moves backward.
- For a reverse iterator,
- Insert iterators
- Inserting, instead of overwriting, elements in a container
- Input, output, forward, bidirectional, random-access iterators
- Input and output iterators have relatively few operators defined
- Random access iterators have many operators defined
- Looks like a class hierarchy, but it is not:
- Instead use generic programming techniques supported by iterator tags – the so-called tag dispatch technique is used to implement compile-time polymorphism.
Operations¶
- All iterators:
++,* - Input iterator
p- Single read (
x = *p), no write,==,!=
- Single read (
- Output iterator
p- Single write (
*p = x), no read
- Single write (
- Forward iterator
- Like input and output iterator, but can read and write repeatedly
- Bidirectional
- Like forward iterator, with an addition of
--
- Like forward iterator, with an addition of
- Random-access
- Like bidirectional iterator, but can add and subtract integers to/from iterators
- Can compare iterators with
<,<=,>, and>=
Insert Iterators¶
- Insertion is normally done by the container operations
push_back(),push_front(), andinsert() - What if I want to make my algorithm independent of of the underlying container?
- Insert iterators a special type of output iterators that can be created by the template factory functions
back_inserter,front_inserter, andinserter. - Example
Containers¶
Three most important types of containers:
- Sequences:
vector,list,forward_list,dequeuestack,queue,priority_queuevia adapters on other sequences - restricting interfaces
- Associative containers
- Based on balanced trees:
map,multimap,set,multiset - Based on hash tables:
unordered_map,unordered_multimap,unordered_set,unordered_multiset
- Based on balanced trees:
- Almost containers
string,valarray,bitsetarray: Contains it elements directly (not a handle to its elements), fixed size - the size is a constant expression. Move is not more efficient than copy
Properties of Containers¶
- No common base class for the standard containers
- However, each container provides standard operations with standard names and semantics
- Non-intrusive containers
- Elements of containers do no need to be instances of certain classes
- An object is not aware of being an element of a particular container
- Values of built-in types can be elements in a container
-
Standard containers rely heavily on templates, operator overloads
-
Small example: using map
Digression: Exceptions¶
Every STL operation provides one of the three guarantees:
- Basic guarantee: Basic invariants of objects are maintained (i.e., objects remain in valid state) and no resources are leaked.
- Strong guarantee: Basic guarantee + either the operation succeeds, or it has no effect.
- For example
push_back()provides it.
- For example
- Nothrow guarantee: Basic guarantee + not throwing an exception.
noexcept¶
Let’s look at std::vector’s push_back() providing the strong guarantee.
- What happens when the vector has to be expanded, by relocating and copying it to a new space?
- For efficiency the copying can be replaced by moving, but only if the moves do not emit exceptions
- That is why it is important to declare your move operations noexcept (as well as other operations, you can guarantee not to throw exceptions)
1 2 | |
Container Member Types¶
Each standard container defines a number of types - via using type aliases. For example:
value_typeiterator,const_iterator,reverse_iterator,const_reverse_iteratorsize_type,difference_typepointer,const_pointerreference,const_reference
It is often possible to avoid use of container member type names - because auto can be used instead
Algorithms¶
The advantages of using STL algorithms:
- Raise the level of abstraction – code is easier to read and maintain!
- Avoid common mistakes:
- Off-by-one errors
- Special cases (like empty collection)
- High quality implementations
- Best algorithmic complexity:
- For example:
std::nth_elementinO(n)time.
- For example:
Overview¶
Wide variety of algorithms – simple building blocks: (see Fluent C++ blog for an alternative classification into 7 categories)
- Non-modifying sequence algorithms
for-each(): Applies a function on each element of a sequencefind(): Find the first element that compares equal to a given value. Return iterator to elementfind_if(): Find the first element that satisfies a given predicate. Return iterator to element
-
Modifying sequence algorithms
transform(): Applies a function on each element, and writes the result into another container.replace(): Replace elements that satisfies a predicate with a another value
-
Sorting and searching algorithms (on sequences):
sortbinary_search
- Set algorithms
set_union,set_intersection,set_difference
- Heap algorithms
make_heap,push_heap, andpop_heap
- Min and max algorithms
minandmaxfor pairs of elementsmax_elementandmin_element: generalization to sequences of elements
- Permutation algorithms
next_permutationandprev_permutation: systematic creation of permutations of a sequence
- Numeric algorithms
Principles¶
- Generalization by function parameters
- Pointers to functions, function objects, or lambda expressions
- Function object is an object for which an application operator,
operator(), has been defined
- A sequence is defined by two iterators
- Beginning at the first element
- Ended by the one-beyond-the-last element
- Failure to find an element in a sequence:
- Return the (off by one) end-of-sequence iterator
- The time complexities of the algorithms are specified by the C++ standard
for_each Example¶
- Let's find out if a sequence is sorted
- Note that
for_eachreturns its third parameter: the function (object) - Example
- Use of objects as functions via an overload of the application operator
- A function surrounded by private, mutable state
- Note that
- A Stroustrup advice: “before using
for_each(), consider if there is a more specialized algorithm that would do more for you”- Such as
accumulate(),find(), oradjacent_find() - Simpler example
greater<T>is a wrapper around the>operator
- Such as
Use of Function Objects in STL¶
- Wrapping operator predicates
- Predicates such as
==,<and> - Available as
equal_to,less, andgreater
- Predicates such as
- Wrapping arithmetic operators
- Operators such as
+and- - Available as
plusandminus
- Operators such as
- Binding one argument of a two argument function
- Allowing a member function to be used as a function
- Negating a predicate