In 2011, the C++ standard unified the use of threading in Modern C++. The standard library now supplies the base building blocks required to implement a threaded software application. The library also addresses specific issues such as memory safety (atomic access is now part of the language).
However, the use of these threading building blocks does not guarantee a reliable application. Building a safe, robust, threaded application requires that the core library features are built up on to create higher-order concurrency patterns.
High-quality software is not just about functionally correctness. It also requires a structure that ensures maintainability, flexibility and extensibility (characteristics of code with high intrinsic quality). This course teaches patterns to increase the intrinsic quality of the concurrent software.
Increased intrinsic quality means:
- The effect of bugs should be constrained, and locating and fixing bugs should be straightforward
- It should require minimal effort to reconfigure the software to suit new purposes
- Adding new functionality should not require a large-scale restructuring of existing code
These are characteristics that must be designed into the software from the ground up. It is usually prohibitively expensive to try and improve the intrinsic quality of software after the event.
Software design patterns originated from the realisation that applying the principles of software intrinsic quality to common problems yielded common solutions.
The course starts by reviewing the Modern C++ language features required to utilise the concurrency library. Next, it introduces the core library building blocks (e.g. thread, mutex, etc.). Finally, it introduces a series of real-time design patterns to support the construction of modern C++ threaded applications.
This course has been developed for C++ developers who are moving to Modern C++ in a concurrent, real-time environment. It focuses on best-practice idioms of the language and patterns specifically formulated to solve common problems encountered in concurrent systems.
Course Objectives:
- To understand what design patterns are and how the language of patterns can aid designers and developers to be more productive
- To develop your understanding of concurrency and how it relates to program design
- To understand the issues with multi-threaded programs and how patterns can help to circumvent these issues.
- To provide practical experience of building concurrent, multi-threaded systems.
Delegates will learn:
- Modern C++ syntax, semantics and library features
- Idioms and patterns for building effective C++ programs
- Real-time and concurrency design issues
- Concurrency design patterns
Pre-requisites:
- A strong working knowledge of C++ (Knowledge of C++11 onwards is useful, but not essential)
- Some experience of development of multi-threaded applications is useful.
- A working understanding of machine architectures is helpful.
Who should attend:
This course is suitable for:
- Experienced Modern C++ programmers who are moving to a concurrent, real-time environment.
- Experienced Modern C++ programmers who are looking to extend their design skills.
- Experienced C++ programmers who are using earlier standards of C++ and are moving to Modern C++.
Duration:
Four days.
Course materials:
- Delegate manual
- Delegate workbook
- Delegate datakey
Course workshop:
Attendees perform hands-on exercises during course practicals. A large percentage of the course is given over to practical work. The tools used are indicative of current modern working practices in the embedded arena.
The C++ object model
- Declaration and definition
- Brace initialisation syntax
- 'The One Declaration Rule' and ODR-use
- Object scope and lifetime
- The C++ object (memory) model
Uniform Initialization syntax
- The difference between initialization and assignment
- Aggregate types – structs
- Brace elision
- Classes
- Non-Static Data Member Initialisers
- Delegating constructors
- std::initializer_list
Functions
- Function call ABIs (Application Binary Interfaces)
- Input, Output and Input-Output parameters
- const correctness
- Copy elision
- Attributes
- Optional return values
Type deduction
- Automatic type deduction
- Automatic function return-type deduction
- Structured bindings
- Using aliases
Constants
Literals
Const qualification
constexpr
constexpr functions
enum class
enum underlying type
Connecting objects
- Unidirectional associations
- Bidirectional association
- Forward declarations
- Composite object construction
Modularisation
- What makes bad software?
- Designing for intrinsic quality
- Coupling
- Cohesion
- Encapsulation
- Abstraction
The SOLID principles
- Specialisation vs inheritance
- The Liskov Substitution principle
- The Single Responsibility principle
- The Open-Closed principle
- Abstract types
- The Dependency Inversion principle
- The Interface concept
- The Interface Segregation principle
Containers
- The STL container model
- Dynamic containers
- Emplacement
- Memory management.
Iterators
- The iterator model
- Range-for
Algorithms
- The STL Algorithm model
- Ranges and half-ranges
- Iterator adapters
- The Remove-erase idiom
Callable objects
- Lambda expressions
- The 'block-scoped function' concept
- Generic lambdas
- std::function
Smart pointers
- The problem with raw pointers for memory management
- std::unique_ptr
- std::shared_ptr
- std::weak_ptr
Threading
- Concurrency vs parallelism
- std::thread
- Run policies
- Polymorphic threads
- Waiting for threads to finish
- Detaching threads
Mutual exclusion
- Race conditions
- Mutual exclusion
- The scope-locked idiom
- std::lock_guard and std::unique_lock
Thread-safe APIs
- When simple mutual exclusion is inadequate
- Race conditions in APIs
- Designing a thread-safe API
- The Adapter pattern
Monitors
- Condition variables
- The Guarded Suspension pattern
Atomic types
- Shared objects in multiprocessor environments
- Why volatile is inappropriate
- Atomic types
- Load-acquire / Store-release barriers
- When atomic types are inadequate
Resource pools
- The multiple-reader, multiple-writer problem
- The Reader-Writer Lock pattern
- std::shared_mutex
Asynchronous messaging
- The Communicating Sequential Processes (CSP) model
- Asynchronous messages
- The Asynchronous Message pattern
- Making std::function asynchronous
- Arguments on asynchronous messages
- std::bind
- Polymorphic asynchronous messages
Promises and Futures
- The Deferred Synchronous Message Pattern
- std::promise and std::future
- Throwing exceptions between tasks
- Packaged tasks
- std::async
- Launch policies
Synchronous messaging
- Synchronous messaging characteristics
- The function-call-as-synchronous-message
- The problems with function calls between tasks
- The Synchronous Message pattern
- Using std::packaged_task for synchronous messages
Finite State Machines
- Reactive Objects
- Finite State Machines to describe behaviour
- Switch-based implementations
- Table-based implementations
- The State pattern
Appendix - Anti-patterns
- Commonly-occurring bad practices
- The Anti-pattern philosophy
- Common anti-patterns in systems