Design Patterns in Modern C++

DmitriNesteruk 3,098 views 111 slides Oct 18, 2016
Slide 1
Slide 1 of 111
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18
Slide 19
19
Slide 20
20
Slide 21
21
Slide 22
22
Slide 23
23
Slide 24
24
Slide 25
25
Slide 26
26
Slide 27
27
Slide 28
28
Slide 29
29
Slide 30
30
Slide 31
31
Slide 32
32
Slide 33
33
Slide 34
34
Slide 35
35
Slide 36
36
Slide 37
37
Slide 38
38
Slide 39
39
Slide 40
40
Slide 41
41
Slide 42
42
Slide 43
43
Slide 44
44
Slide 45
45
Slide 46
46
Slide 47
47
Slide 48
48
Slide 49
49
Slide 50
50
Slide 51
51
Slide 52
52
Slide 53
53
Slide 54
54
Slide 55
55
Slide 56
56
Slide 57
57
Slide 58
58
Slide 59
59
Slide 60
60
Slide 61
61
Slide 62
62
Slide 63
63
Slide 64
64
Slide 65
65
Slide 66
66
Slide 67
67
Slide 68
68
Slide 69
69
Slide 70
70
Slide 71
71
Slide 72
72
Slide 73
73
Slide 74
74
Slide 75
75
Slide 76
76
Slide 77
77
Slide 78
78
Slide 79
79
Slide 80
80
Slide 81
81
Slide 82
82
Slide 83
83
Slide 84
84
Slide 85
85
Slide 86
86
Slide 87
87
Slide 88
88
Slide 89
89
Slide 90
90
Slide 91
91
Slide 92
92
Slide 93
93
Slide 94
94
Slide 95
95
Slide 96
96
Slide 97
97
Slide 98
98
Slide 99
99
Slide 100
100
Slide 101
101
Slide 102
102
Slide 103
103
Slide 104
104
Slide 105
105
Slide 106
106
Slide 107
107
Slide 108
108
Slide 109
109
Slide 110
110
Slide 111
111

About This Presentation

Slides from my 1st and 2nd talks on Design Patterns in Modern C++.


Slide Content

Design Patterns in
Modern C++, Part I
Dmitri Nesteruk
dmitrinеstеruk@gmаil.соm
@dnesteruk

What’s In This Talk?
•Examples of patterns and approaches in OOP design
•Adapter
•Composite
•Specification pattern/OCP
•Fluent and Groovy-style builders

Maybe monad

Adapter

STL String Complaints
•Making a string is easy
string s{“hello world”};

Getting its constituent parts is not
vector<strings> words;
boost::split(words, s, boost::is_any_of(“ “));

Instead I would prefer
auto parts = s.split (“ “);

It should work with “hello world”

Maybe some other goodies, e.g.
•Hide size()

Have length()as a property, not a function

Basic Adapter
class String {
string s;
public:
String(conststring &s) : s{ s } { }
};

Implement Split
class String {
string s;
public:
String(const string &s) : s{ s } { }
vector<string> split(string input)
{
vector<string> result;
boost::split(result, s,
boost::is_any_of(input), boost::token_compress_on);
return result;
}
};

Length Proxying
class String {
string s;
public:
String(conststring &s) : s{ s } { }
vector<string> split(string input);
size_tget_length() const{ return s.length(); }
};

Length Proxying
class String {
string s;
public:
String(conststring &s) : s{ s } { }
vector<string> split(string input);
size_tget_length() const{ return s.length(); }
// non-standard!
__declspec(property(get = get_length)) size_tlength;
};

String Wrapper Usage
String s{ "hello world" };
cout<< "string has " <<
s.length<< " characters" << endl;
auto words = s.split(" ");
for (auto& word : words)
cout<< word << endl;

Adapter Summary
•Aggregate objects (or keep a reference)
•Can aggregate more than one
•E.g., string and formatting
•Replicate the APIs you want (e.g., length)
•Miss out on the APIs you don’t need
•Add your own features :)

Composite

Scenario
•Neurons connect to other
neurons

Neuron layersare collections of
neurons

These two need to be
connectable

Scenario
structNeuron
{
vector<Neuron*> in, out;
unsigned int id;
Neuron()
{
static int id = 1;
this->id = id++;
}
}

Scenario
structNeuronLayer: vector<Neuron>
{
NeuronLayer(intcount)
{
while (count--> 0)
emplace_back(Neuron{});
}
}

State Space Explosition
•void connect_to(Neuron& other)
{
out.push_back(&other);
other.in.push_back(this);
}

Unfortunately, we need 4 functions
•Neuron to Neuron
•Neuron to NeuronLayer

NeuronLayerto Neuron

NeuronLayerto NeuronLayer

One Function Solution?
•Simple: treat Neuron as NeuronLayerof size 1
•Not strictly correct
•Does not take into account other concepts (e.g., NeuronRing)
•Better: expose a single Neuron in an iterablefashion

Other programming languages have interfaces for iteration
•E.g., C# IEnumerable<T>

yieldkeyword
•C++ does duck typing
•Expects begin/end pair
•One function solution not possible, but…

Generic Connection Function
structNeuron
{
...
template <typename T> void connect_to(T& other)
{
for (Neuron& to : other)
connect_to(to);
}
template<> void connect_to<Neuron>(Neuron& other)
{
out.push_back(&other);
other.in.push_back(this);
}
};

Generic Connection Function
structNeuronLayer: vector<Neuron>
{

template <typenameT> void connect_to (T& other)
{
for (Neuron& from : *this)
for (Neuron& to : other)
from.connect_to(to);
}
};

How to Iterate on a Single Value?
structNeuron
{

Neuron* begin() { return this; }
Neuron* end() { return this + 1; }
};

API Usage
Neuron n, n2;
NeuronLayernl, nl2;
n.connect_to(n2);
n.connect_to(nl);
nl.connect_to(n);
nl.connect_to(nl2);

Specification Pattern and the OCP

Open-Closed Principle
•Open for extension, closed for modification
•Bad: jumping into old code to change a stable, functioning system
•Good: making things generic enough to be externally extensible
•Example: product filtering

Scenario
enumclass Color{ Red, Green, Blue };
enumclass Size { Small, Medium, Large };
structProduct
{
std::string name;
Colorcolor;
Size size;
};

Filtering Products
structProductFilter
{
typedefstd::vector<Product*> Items;
static Items by_color(Items items, Colorcolor)
{
Items result;
for (auto& i : items)
if (i ->color== color)
result.push_back( i);
return result;
}
}

Filtering Products
structProductFilter
{
typedefstd::vector<Product*> Items;
static Items by_color(Items items, Colorcolor) { … }
static Items by_size(Items items, Size size)
{
Items result;
for (auto& i : items)
if (i ->size == size)
result.push_back( i);
return result;
}
}

Filtering Products
structProductFilter
{
typedefstd::vector<Product*> Items;
static Items by_color(Items items, Colorcolor) { … }
static Items by_size(Items items, Size size) { … }
static Items by_color_and_size(Items items, Size size, Colorcolor)
{
Items result;
for (auto& i: items)
if (i ->size == size && i ->color== color)
result.push_back( i);
return result;
}
}

Violating OCP
•Keep having to rewrite existing code
•Assumes it is even possible (i.e. you have access to source code)
•Not flexible enough (what about other criteria?)
•Filtering by X or Y or X&Y requires 3 functions
•More complexity -> state space explosion
•Specification pattern to the rescue!

ISpecificationand IFilter
template <typenameT> structISpecification
{
virtual bool is_satisfied(T* item) = 0;
};
template <typenameT> structIFilter
{
virtual std::vector<T*> filter(
std::vector<T*> items,
ISpecification<T>& spec) = 0;
};

A Better Filter
structProductFilter: IFilter<Product>
{
typedefstd::vector<Product*> Products;
Products filter(
Products items,
ISpecification<Product>& spec) override
{
Products result;
for (auto& p : items)
if (spec.is_satisfied(p))
result.push_back(p);
return result;
}
};

Making Specifications
structColorSpecification: ISpecification<Product>
{
Colorcolor;
explicit ColorSpecification(constColorcolor)
: color{color} { }
bool is_satisfied(Product* item) override {
return item->color== color;
}
}; // same for SizeSpecification

Improved Filter Usage
Product apple{ "Apple", Color ::Green, Size::Small };
Product tree { "Tree", Color::Green, Size::Large };
Product house{ "House", Color ::Blue, Size::Large };
std::vector<Product*> all{ &apple, &tree, &house };
ProductFilterpf;
ColorSpecificationgreen(Color::Green);
auto green_things= pf.filter(all, green);
for (auto& x : green_things)
std::cout<< x- >name << " is green" << std::endl;

Filtering on 2..N criteria
•How to filter by size andcolor?

We don’t want a SizeAndColorSpecification
•State space explosion
•Create combinators
•A specification which combinestwo other specifications

E.g., AndSpecification

AndSpecificationCombinator
template <typename T> structAndSpecification: ISpecification<T>
{
ISpecification<T>& first;
ISpecification<T>& second;
AndSpecification( ISpecification<T>& first,
ISpecification<T>& second)
: first{first}, second{second} { }
bool is_satisfied(T* item) override
{
return first.is_satisfied(item) && second.is_satisfied(item);
}
};

Filtering by Size AND Color
ProductFilterpf;
ColorSpecificationgreen(Color ::Green);
SizeSpecificationbig(Size::Large);
AndSpecification<Product> green_and_big{ big, green };
auto big_green_things = pf.filter(all, green_and_big);
for (auto& x : big_green_things)
std::cout<< x- >name << " is big and green" << std::endl;

Specification Summary
•Simple filtering solution is
•Too difficult to maintain, violates OCP
•Not flexible enough
•Abstract away the specification interface
•bool is_satisfied_by(T something)
•Abstract away the idea of filtering
•Input items + specification set of filtered items
•Create combinators(e.g., AndSpecification) for combining multiple
specifications

Fluent and Groovy-Style Builders

Scenario
•Consider the construction of structured data
•E.g., an HTML web page
•Stucturedand formalized

Rules (e.g., P cannot contain another P)
•Can we provide an API for building these?

Building a Simple HTML List
// <ul ><li>hello</li><li>world</li></ul >
string words[] = { "hello", "world" };
ostringstreamoss;
oss<< "<ul>";
for (auto w : words)
oss<< " <li>" << w << "</li>";
oss<< "</ul>";
printf(oss.str(). c_str());

HtmlElement
structHtmlElement
{
string name;
string text;
vector<HtmlElement> elements;
constsize_tindent_size= 2;
string str(intindent = 0) const; // pretty -print
}

Html Builder (non-fluent)
structHtmlBuilder
{
HtmlElementroot;
HtmlBuilder(string root_name) { root.name = root_name; }
void add_child(string child_name, string child_text)
{
HtmlElemente{ child_name, child_text };
root.elements.emplace_back(e);
}
string str() { return root.str(); }
}

Html Builder (non-fluent)
HtmlBuilderbuilder{"ul"};
builder.add_child("li", "hello")
builder.add_child("li", "world");
cout<< builder.str() << endl ;

Making It Fluent
structHtmlBuilder
{
HtmlElementroot;
HtmlBuilder(string root_name) { root.name = root_name; }
HtmlBuilder& add_child(string child_name, string child_text)
{
HtmlElemente{ child_name, child_text};
root.elements.emplace_back(e);
return *this;
}
string str() { return root.str(); }
}

Html Builder
HtmlBuilderbuilder{"ul"};
builder.add_child("li", "hello").add_child("li", "world");
cout<< builder.str() << endl ;

Associate Builder & Object Being Built
structHtmlElement
{
static HtmlBuilderbuild(string root_name)
{
return HtmlBuilder{root_name};
}
};
// usage:
HtmlElement::build("ul ")
.add_child_2("li", "hello").add_child_2("li", "world");

Groovy-Style Builders
•Express the structureof the HTML in code

No visible function calls
•UL {
LI {“hello”},
LI {“world”}
}

Possible in C++ using uniform initialization

Tag (= HTML Element)
structTag
{
string name;
string text;
vector<Tag> children;
vector<pair<string, string>> attributes;
protected:
Tag(const std::string& name, const std::string& text)
: name{name}, text{text} { }
Tag(const std::string& name, const std::vector<Tag>& children)
: name{name}, children{children} { }
}

Paragraph
structP : Tag
{
explicit P(conststd::string& text)
: Tag{"p", text}
{
}
P(std::initializer_list<Tag> children)
: Tag("p", children)
{
}
};

Image
structIMG : Tag
{
explicit IMG(conststd::string& url )
: Tag{"img", ""}
{
attributes.emplace_back(make_pair(" src", url));
}
};

Example Usage
std::cout<<
P {
IMG {"http://pokemon.com/pikachu.png"}
}
<< std::endl;

Facet Builders
•An HTML element has different facets
•Attributes, inner elements, CSS definitions, etc.
•A Person class might have different facets
•Address
•Employment information
•Thus, an object might necessitate severalbuilders

Personal/Work Information
class Person
{
// address
std::string street_address , post_code, city;
// employment
std::string company_name , position;
intannual_income= 0;
Person() {} // private!
}

Person Builder (Exposes Facet Builders)
class PersonBuilder
{
Person p;
protected:
Person& person;
explicit PersonBuilder(Person& person)
: person{ person } { }
public:
PersonBuilder() : person{p} { }
operator Person() { return std::move(person); }
// builder facets
PersonAddressBuilderlives();
PersonJobBuilderworks();
}

Person Builder (Exposes Facet Builders)
class PersonBuilder
{
Person p;
protected:
Person& person;
explicit PersonBuilder(Person& person)
: person{ person } { }
public:
PersonBuilder() : person{p} { }
operator Person() { return std::move(person); }
// builder facets
PersonAddressBuilderlives();
PersonJobBuilderworks();
}

Person Builder Facet Functions
PersonAddressBuilderPersonBuilder::lives()
{
return PersonAddressBuilder{ person };
}
PersonJobBuilderPersonBuilder::works()
{
return PersonJobBuilder{ person };
}

Person Address Builder
class PersonAddressBuilder: public PersonBuilder
{
typedefPersonAddressBuilderSelf;
public:
explicit PersonAddressBuilder(Person& person)
: PersonBuilder{ person } { }
Self& at(std::string street_address)
{
person.street_address= street_address;
return *this;
}
Self& with_postcode( std::string post_code);
Self& in(std::string city);
};

Person Job Builder
class PersonJobBuilder: public PersonBuilder
{
typedefPersonJobBuilderSelf;
public:
explicit PersonJobBuilder (Person& person)
: PersonBuilder{ person } { }
Self& at(std::string company_name );
Self& as_a(std::string position);
Self& earning(int annual_income);
};

Back to Person
class Person
{
// fields
public:
static PersonBuildercreate();
friend class PersonBuilder;
friend class PersonAddressBuilder ;
friend class PersonJobBuilder;
};

Final Person Builder Usage
Person p = Person::create()
.lives().at("123 London Road")
.with_postcode("SW1 1GB")
.in("London")
.works().at("PragmaSoft")
.as_a("Consultant")
.earning(10e6);

Maybe Monad

Presence or Absence
•Different ways of expressing absence of value
•Default-initialized value
•string s; // there is no ‘null string’
•Null value
•Address* address;
•Not-yet-initialized smart pointer
•shared_ptr<Address> address
•Idiomatic
•boost::optional

Monads
•Design patterns in functional programming
•First-class function support

Related concepts
•Algebraic data types
•Pattern matching
•Implementable to some degreein C++
•Functional objects/lambdas

Scenario
structAddress
{
char* house_name; // why not string?
}
structPerson
{
Address* address;
}

Print House Name, If Any
void print_house_name(Person* p)
{
if (p != nullptr&&
p->address != nullptr&&
p->address->house_name!= nullptr)
{
cout<< p->address->house_name<< endl;
}
}

Maybe Monad
•Encapsulate the ‘drill down’ aspect of code
•Construct a Maybe<T> which keeps context
•Context: pointer to evaluated element
•person -> address -> name
•While context is non-null, we drill down

If context is nullptr, propagation does not happen

All instrumented using lambdas

Maybe<T>
template<typenameT>
structMaybe{
T* context;
Maybe(T *context) : context( context) { }
};
// but, given Person* p, we cannotmakea ‘new Maybe(p)’
template <typename T> Maybe<T> maybe(T* context)
{
return Maybe<T>(context);
}

Usage So Far
voidprint_house_name(Person* p)
{
maybe(p). // nowdrill down :)
}

Maybe::With
template<typenameT> struct Maybe
{
...
template <typenameTFunc>
auto With(TFuncevaluator)
{
if (context == nullptr )
return ??? // cannot return maybe(nullptr) :(
return maybe(evaluator(context));
};
}

What is ???
•In case of failure, we need to return Maybe<U>
•But the type of U should be the return type of evaluator
•But evaluator returns U* and we need U
•Therefore…
•return Maybe<
typenameremove_pointer<
decltype(evaluator(context))
>::type>(nullptr);

Maybe::With Finished
template<typenameTFunc>
auto With( TFuncevaluator)
{
if (context== nullptr)
return Maybe< typenameremove_pointer<
decltype( evaluator( context))>::type>(nullptr);
return maybe(evaluator( context));
};

Usage So Far
voidprint_house_name(Person* p)
{
maybe(p) // nowdrill down :)
.With([](auto x) { return x->address; })
.With([](auto x) { return x->house_name; })
. // print here (if context is not null)
}

Maybe::Do
template <typenameTFunc>
auto Do(TFuncaction)
{
// if context is OK, perform action on it
if (context != nullptr) action(context);
// no context transition, so...
return *this;
}

How It Works
•print_house_name(nullptr)
•Context is null from the outset and continues to be null
•Nothing happensin the entire evaluation chain
•Person p; print_house_name(&p);
•Context is Person, but since Address is null, it becomes null henceforth
•Person p;
p->Address = new Address;
p->Address->HouseName= “My Castle”;
print_house_name(&p);
•Everything works and we get our printout

Maybe Monad Summary
•Example is not specific to nullptr
•E.g., replace pointers with boost::optional
•Default-initialized types are harder
•If s.length() == 0, has it been initialized?
•Monads are difficult due to lack of functional support
•[](auto x) { return f(x); }instead of
x => f(x)as in C#

No implicits(e.g. Kotlin’s‘it’)

That’s It!
•Questions?
•Design Patterns in C++ courses on Pluralsight

dmitrinеsteruk/at/ gmail.com

@dnesteruk

Design Patterns in Modern
C++, Part II
Dmitri Nesteruk
dmitrinеstеruk@gmаil.соm
@dnesteruk

What’s In This Talk?
•Part II of my Design Patterns talks
•Part I of the talk available online in Englishand Russian
•Examples of design patterns implemented in C++
•Disclaimer: C++ hasn’t internalized any patterns (yet)
•Patterns
•Memento
•Visitor
•Observer
•Interpreter

Memento
Helping implement undo/redo

Bank Account
•Bank account has a balance
•Balance changed via
•Withdrawals
•Deposits
•Want to be able to undo an erroneous
transaction

Want to navigate to an arbitrary point
in the account’s changeset
class BankAccount
{
intbalance{0};
public:
void deposit(int x) {
balance += x;
}
void withdraw(int x) {
if (balance >= x)
balance -= x;
}
};

Memento (a.k.a. Token, Cookie)
class Memento
{
intbalance;
public:
Memento(intbalance)
: balance(balance)
{
}
friend class BankAccount;
};
• Keeps the state of the balance at
a particular point in time
•State is typically private
•Can also keep reason for latest
change, amount, etc.

Returned during bank account
changes

Memento can be used to restore
object to a particular state

Deposit and Restore
Memento deposit(int amount)
{
balance += amount;
return { balance };
}
void restore(const Memento& m)
{
balance = m.balance;
}
BankAccountba{ 100 };
auto m1 = ba.deposit(50); // 150
auto m2 = ba.deposit(25); // 175
// undo to m1
ba.restore(m1); // 150
// redo
ba.restore(m2); // 175

Storing Changes
class BankAccount{
intbalance{0}, current;
vector<shared_ptr<Memento>> changes;
public:
BankAccount( constintbalance) : balance(balance)
{
changes.emplace_back( make_shared<Memento>(balance));
current = 0;
}
};

Undo and Redo
shared_ptr<Memento> deposit(int
amount)
{
balance += amount;
auto m = make_shared<Memento>(
balance);
changes.push_back(m);
++current;
return m;
}
shared_ptr<Memento> undo()
{
if (current > 0)
{
--current;
auto m = changes[current];
balance = m->balance;
return m;
}
return{};
}

Undo/Redo Problems
•Storage excess
•Need to store only changes, but still…
•Need to be able to overwrite the Redo step
•Undo/redo is typically an aggregate operation

Cookie Monster!
•Why doesn’t push_back() return a
reference?

Well, it could, but…
•As long as you’re not resizing due
to addition

How to refer to an element of
vector<int>?
•Dangerous unless append-only

Change to vector<shared_ptr<int>>

Change to list<int>

Some range-tracking magic token
solution
•Return a magic_cookiethat
•Lets you access an element provided
it exists

Is safe to use if element has been
deleted

Keeps pointing to the correct element
even when container is reordered
•Requires tracking all mutating
operations

Is it worth it?

Observer
This has been done to death, but…

Simple Model
class Person
{
intage;
public:
void set_age( intage)
{
this- >age = age;
}
intget_age() const
{
return age;
}
};
• Person has an age: private field
with accessor/mutator

We want to be informed
whenever age changes

Need to modify the setter!

Person Listener
structPersonListener
{
virtual ~PersonListener() = default;
virtual void person_changed(Person& p,
conststring& property_name, constany new_value) = 0;
};

Person Implementation
class Person
{
vector<PersonListener*> listeners;
public:
void subscribe(PersonListener* pl) {
listeners.push_back(pl);
}
void notify(const string& property_name, const any new_value)
{
for (const auto listener : listeners)
listener->person_changed(*this, property_name, new_value);
}
};

Setter Change
void set_age( constintage)
{
if (this- >age == age) return;
this- >age = age;
notify("age", this- >age);
}

Consumption
structConsoleListener: PersonListener
{
void person_changed(Person& p,
conststring& property_name,
constany new_value) override
{
cout<< "person's " << property_name
<< " has been changed to ";
if (property_name == "age")
{
cout<< any_cast<int>(new_value);
}
cout<< "\n";
}
}; Person p{14};
ConsoleListenercl;
p.subscribe(&cl);
p.set_age(15);
p.set_age(16);

Dependent Property
bool get_can_vote() const
{
return age >= 16;
}
• Where to notify?
•How to detect that any
affecting property has
changed?

Can we generalize?

Notifying on Dependent Property
void set_age( constintage)
{
if (this- >age == age) return;
auto old_c_v= get_can_vote();
this- >age = age;
notify("age", this- >age);
auto new_c_v= get_can_vote();
if (old_c_v != new_c_v)
{
notify("can_vote ", new_c_v);
}
}
save old value
get new value
compare and notify only if
changed

Observer Problems
•Multiple subscriptions by a single listener
•Are they allowed? If not, use std::set
•Un-subscription
•Is it supported?
•Behavior if many subscriptions/one listener?
•Thread safety
•Reentrancy

Thread Safety
static mutexmtx;
class Person {

void subscribe(PersonListener * pl)
{
lock_guard< mutex> guard{mtx };

}
void unsubscribe(PersonListener * pl)
{
lock_guard< mutex> guard{mtx };
for (auto it : listeners)
{
if (*it == pl) *it = nullptr;
// erase- remove in notify()
}
}
}; •Anything that touches the list of subscribers
is locked
•Reader-writer locks better (shared_lockfor
reading, unique_lockfor writing)
•Unsubscriptionsimply nulls the listener
•Must check for nullptr

Remove at the end of notify()
•Alternative: use concurrent_vector
•Guaranteed thread-safe addition

No easy removal

Reentrancy
structTrafficAdministration: Observer<Person>
{
void field_changed(Person& source,
conststring& field_name)
{
if (field_name == "age")
{
if (source.get_age() < 17)
cout<< "Not old enough to drive!\ n";
else
{
// let's not monitor them anymore
cout<< "We no longer care!\ n";
source.unsubscribe(this);
}}}};
Age changes (1617):
•notify() called
•Lock taken
field_changed
•unsubscribe()
unsubscribe()
•Tries to take a lock
•But it’s already taken 

Observer Problems
•Move from mutexto recursive_mutex

Doesn’t solve all problems

See
Thread-safe Observer Pattern – You’re doing it Wrong(Tony Van
Eerd)

Boost.Signals2
•signal<T>
•A signal that can be sent to anyone willing
to listen

T is the type of the slot function
•A slot is the function that receives the
signal
•Ordinary function
•Functor, std::function

Lambda
•Connection
•signal<void()> s;
creates a signal

auto c = s.connect([](){
cout<< “test” << endl;
});
connects the signal to the slot
•More than one slot can be connected to a
signal

Disconnection
•c.disconnect();

Disconnects allslots
•Slots can be blocked
•Temporarily disabled
•Used to prevent infinite recursion
•shared_connection_block(c)

Unblocked when block is destroyed, or
explicitly via
block.unblock();

INotifyPropertyChanged<T>
template <typename T>
structINotifyPropertyChanged
{
virtual ~INotifyPropertyChanged() = default;
signal<void(T&, conststring&)> property_changed;
};
structPerson : INotifyPropertyChanged<Person>
{
void set_age(constintage)
{
if (this- >age == age) return;
this- >age = age;
property_changed(*this, "age");
}
};

Consuming INPC Model
Person p{19};
p.property_changed.connect(
[](Person&, conststring& prop_name)
{
cout<< prop_name<< " has been changed" << endl;
});
p.set_age(20);

Interpreter
Make your own programming language!

Interpreter
•Interpret textualinput
•A branch of computer science
•Single item: atoi, lexical_cast, etc.

Custom file format: XML, CSV
•Embedded DSL: regex
•Own programming language

Interpreting Numeric Expressions
(13-4)-(12+1)
Lex: [(] [13] [-] [4] [)] [-] …
Parse: Op(- , Op(-, 13, 4), Op(+,12,1))

Token
structToken
{
enumType {
integer, plus, minus,
lparen, rparen
} type;
string text;
explicit Token(Type type,
conststring& text) :
type{type}, text{text} {}
};

Lexing
vector<Token> lex(conststring& input)
{
vector<Token> result;
for (int i= 0; i< input.size(); ++ i)
{
switch (input[i])
{
case '+':
result.push_back(Token{ Token::plus, "+" });
break;
case '- ':
result.push_back(Token{ Token::minus, " -" });
break;
case '(':
result.push_back(Token{ Token:: lparen, "(" });
break;
case ')':
result.push_back(Token{ Token:: rparen, ")" });
break;
default:
ostringstreambuffer;
buffer << input[i];
for (int j = i+ 1; j < input.size(); ++j)
{
if (isdigit (input[j]))
{
buffer << input[j];
++i;
}
else
{
result.push_back(
Token{ Token::integer, buffer.str() });
break;
}
}

Parsing Structures
structElement
{
virtual ~Element() = default;
virtual inteval() const= 0;
};
structInteger : Element
{
intvalue;
explicit Integer(const intvalue)
: value(value)
{
}
inteval() constoverride {
return value;
}
};
structBinaryOperation: Element
{
enumType { addition, subtraction } type;
shared_ptr<Element> lhs, rhs;
inteval() constoverride
{
if (type == addition)
return lhs- >eval() + rhs->eval();
return lhs- >eval() -rhs->eval();
}
};
shared_ptr<Element> parse(const vector<Token>&
tokens)
{

}

Parsing Numeric Expressions
string input{ "(13- 4)-(12+1)" };
auto tokens = lex(input);
try {
auto parsed = parse(tokens);
cout<< input << " = " << parsed- >eval() << endl;
}
catch (const exception& e)
{
cout<< e.what() << endl;
}

Boost.Sprit
•A Boost library for interpreting text
•Uses a de facto DSL for defining the parser
•Defining a separate lexernot mandatory

Favors Boost.Variantfor polymorphic types

Structurally matches definitions to OOP structures

Tlön Programming Language
•Proof-of-concept language transpiledinto C++

Parser + pretty printer + notepadlikeapp

Some language features
•Shorter principal integral types
•Primary constructors
•Tuples
•Non-keyboard characters (keyboard-unfriendly)

Visitor

Adding Side Functionality to Classes
•C++ Committee not liking UCS
•Or any other “extension function” kind of deal
•Possible extensions on hierarchies end up being intrusive
•Need to modify the entire hierarchy

That’s It!
•Design Patterns in C++ courses on Pluralsight
•Leanpub book (work in progress!)
•Tlön Programming Language
•dmitrinеsteruk/at/ gmail.com

@dnesteruk