SlideShare a Scribd company logo
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(const string &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(const string &s) : s{ s } { }
vector<string> split(string input);
size_t get_length() const { return s.length(); }
};
Length Proxying
class String {
string s;
public:
String(const string &s) : s{ s } { }
vector<string> split(string input);
size_t get_length() const { return s.length(); }
// non-standard!
__declspec(property(get = get_length)) size_t length;
};
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 layers are collections of
neurons
• These two need to be
connectable
Scenario
struct Neuron
{
vector<Neuron*> in, out;
unsigned int id;
Neuron()
{
static int id = 1;
this->id = id++;
}
}
Scenario
struct NeuronLayer : vector<Neuron>
{
NeuronLayer(int count)
{
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
• NeuronLayer to Neuron
• NeuronLayer to NeuronLayer
One Function Solution?
• Simple: treat Neuron as NeuronLayer of size 1
• Not strictly correct
• Does not take into account other concepts (e.g.,NeuronRing)
• Better: expose a single Neuron in an iterable fashion
• Other programming languages have interfaces for iteration
• E.g., C# IEnumerable<T>
• yield keyword
• C++ does duck typing
• Expects begin/end pair
• One function solution not possible, but…
Generic Connection Function
struct Neuron
{
...
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
struct NeuronLayer : vector<Neuron>
{
…
template <typename T> void connect_to(T& other)
{
for (Neuron& from : *this)
for (Neuron& to : other)
from.connect_to(to);
}
};
How to Iterate on a Single Value?
struct Neuron
{
…
Neuron* begin() { return this; }
Neuron* end() { return this + 1; }
};
API Usage
Neuron n, n2;
NeuronLayer nl, 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
enum class Color { Red, Green, Blue };
enum class Size { Small, Medium, Large };
struct Product
{
std::string name;
Color color;
Size size;
};
Filtering Products
struct ProductFilter
{
typedef std::vector<Product*> Items;
static Items by_color(Items items, Color color)
{
Items result;
for (auto& i : items)
if (i->color == color)
result.push_back(i);
return result;
}
}
Filtering Products
struct ProductFilter
{
typedef std::vector<Product*> Items;
static Items by_color(Items items, Color color) { … }
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
struct ProductFilter
{
typedef std::vector<Product*> Items;
static Items by_color(Items items, Color color) { … }
static Items by_size(Items items, Size size) { … }
static Items by_color_and_size(Items items, Size size, Color color)
{
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!
ISpecification and IFilter
template <typename T> struct ISpecification
{
virtual bool is_satisfied(T* item) = 0;
};
template <typename T> struct IFilter
{
virtual std::vector<T*> filter(
std::vector<T*> items,
ISpecification<T>& spec) = 0;
};
A Better Filter
struct ProductFilter : IFilter<Product>
{
typedef std::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
struct ColorSpecification : ISpecification<Product>
{
Color color;
explicit ColorSpecification(const Color color)
: 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 };
ProductFilter pf;
ColorSpecification green(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 and color?
• We don’t want a SizeAndColorSpecification
• State space explosion
• Create combinators
• A specification which combines two other specifications
• E.g., AndSpecification
AndSpecification Combinator
template <typename T> struct AndSpecification : 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
ProductFilter pf;
ColorSpecification green(Color::Green);
SizeSpecification big(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
• Stuctured and 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" };
ostringstream oss;
oss << "<ul>";
for (auto w : words)
oss << " <li>" << w << "</li>";
oss << "</ul>";
printf(oss.str().c_str());
HtmlElement
struct HtmlElement
{
string name;
string text;
vector<HtmlElement> elements;
const size_t indent_size = 2;
string str(int indent = 0) const; // pretty-print
}
Html Builder (non-fluent)
struct HtmlBuilder
{
HtmlElement root;
HtmlBuilder(string root_name) { root.name = root_name; }
void add_child(string child_name, string child_text)
{
HtmlElement e{ child_name, child_text };
root.elements.emplace_back(e);
}
string str() { return root.str(); }
}
Html Builder (non-fluent)
HtmlBuilder builder{"ul"};
builder.add_child("li", "hello")
builder.add_child("li", "world");
cout << builder.str() << endl;
Making It Fluent
struct HtmlBuilder
{
HtmlElement root;
HtmlBuilder(string root_name) { root.name = root_name; }
HtmlBuilder& add_child(string child_name, string child_text)
{
HtmlElement e{ child_name, child_text };
root.elements.emplace_back(e);
return *this;
}
string str() { return root.str(); }
}
Html Builder
HtmlBuilder builder{"ul"};
builder.add_child("li", "hello").add_child("li", "world");
cout << builder.str() << endl;
Associate Builder & Object Being Built
struct HtmlElement
{
static HtmlBuilder build(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 structure of the HTML in code
• No visible function calls
• UL {
LI {“hello”},
LI {“world”}
}
• Possible in C++ using uniform initialization
Tag (= HTML Element)
struct Tag
{
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
struct P : Tag
{
explicit P(const std::string& text)
: Tag{"p", text}
{
}
P(std::initializer_list<Tag> children)
: Tag("p", children)
{
}
};
Image
struct IMG : Tag
{
explicit IMG(const std::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 several builders
Personal/Work Information
class Person
{
// address
std::string street_address, post_code, city;
// employment
std::string company_name, position;
int annual_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
PersonAddressBuilder lives();
PersonJobBuilder works();
}
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
PersonAddressBuilder lives();
PersonJobBuilder works();
}
Person Builder Facet Functions
PersonAddressBuilder PersonBuilder::lives()
{
return PersonAddressBuilder{ person };
}
PersonJobBuilder PersonBuilder::works()
{
return PersonJobBuilder{ person };
}
Person Address Builder
class PersonAddressBuilder : public PersonBuilder
{
typedef PersonAddressBuilder Self;
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
{
typedef PersonJobBuilder Self;
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 PersonBuilder create();
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 degree in C++
• Functional objects/lambdas
Scenario
struct Address
{
char* house_name; // why not string?
}
struct Person
{
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 <typename T>
struct Maybe {
T* context;
Maybe(T *context) : context(context) { }
};
// but, given Person* p, we cannot make a ‘new Maybe(p)’
template <typename T> Maybe<T> maybe(T* context)
{
return Maybe<T>(context);
}
Usage So Far
void print_house_name(Person* p)
{
maybe(p). // now drill down :)
}
Maybe::With
template <typename T> struct Maybe
{
...
template <typename TFunc>
auto With(TFunc evaluator)
{
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<
typename remove_pointer<
decltype(evaluator(context))
>::type>(nullptr);
Maybe::With Finished
template <typename TFunc>
auto With(TFunc evaluator)
{
if (context == nullptr)
return Maybe<typename remove_pointer<
decltype(evaluator(context))>::type>(nullptr);
return maybe(evaluator(context));
};
Usage So Far
void print_house_name(Person* p)
{
maybe(p) // now drill down :)
.With([](auto x) { return x->address; })
.With([](auto x) { return x->house_name; })
. // print here (if context is not null)
}
Maybe::Do
template <typename TFunc>
auto Do(TFunc action)
{
// 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 happens in 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 inEnglish and 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
{
int balance{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
{
int balance;
public:
Memento(int balance)
: 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;
}
BankAccount ba{ 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 {
int balance{0}, current;
vector<shared_ptr<Memento>> changes;
public:
BankAccount(const int balance) : 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_cookie that
• 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
{
int age;
public:
void set_age(int age)
{
this->age = age;
}
int get_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
struct PersonListener
{
virtual ~PersonListener() = default;
virtual void person_changed(Person& p,
const string& property_name, const any 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(const int age)
{
if (this->age == age) return;
this->age = age;
notify("age", this->age);
}
Consumption
struct ConsoleListener : PersonListener
{
void person_changed(Person& p,
const string& property_name,
const any 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};
ConsoleListener cl;
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(const int age)
{
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 mutex mtx;
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_lock for
reading, unique_lock for writing)
• Unsubscription simply 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
struct TrafficAdministration : Observer<Person>
{
void field_changed(Person& source,
const string& 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 mutex to 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 all slots
• 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>
struct INotifyPropertyChanged
{
virtual ~INotifyPropertyChanged() = default;
signal<void(T&, const string&)> property_changed;
};
struct Person : INotifyPropertyChanged<Person>
{
void set_age(const int age)
{
if (this->age == age) return;
this->age = age;
property_changed(*this, "age");
}
};
Consuming INPC Model
Person p{19};
p.property_changed.connect(
[](Person&, const string& prop_name)
{
cout << prop_name << " has been changed" << endl;
});
p.set_age(20);
Interpreter
Make your own programming language!
Interpreter
• Interpret textual input
• 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
struct Token
{
enum Type {
integer, plus, minus,
lparen, rparen
} type;
string text;
explicit Token(Type type,
const string& text) :
type{type}, text{text} {}
};
Lexing
vector<Token> lex(const string& 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:
ostringstream buffer;
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
struct Element
{
virtual ~Element() = default;
virtual int eval() const = 0;
};
struct Integer : Element
{
int value;
explicit Integer(const int value)
: value(value)
{
}
int eval() const override {
return value;
}
};
struct BinaryOperation : Element
{
enum Type { addition, subtraction } type;
shared_ptr<Element> lhs, rhs;
int eval() const override
{
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 lexer not mandatory
• Favors Boost.Variant for polymorphic types
• Structurally matches definitions to OOP structures
Tlön Programming Language
• Proof-of-concept language transpiled into C++
• Parser + pretty printer +notepadlike app
• 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

More Related Content

PPTX
Reactive Java (33rd Degree)
PPTX
Python Programming Essentials - M25 - os and sys modules
PPTX
Chapter 06 constructors and destructors
PPTX
Java 8 Lambda and Streams
PPTX
GCC RTL and Machine Description
PPT
Java collections concept
PPTX
Basic Concepts of OOPs (Object Oriented Programming in Java)
PDF
Python Functions Tutorial | Working With Functions In Python | Python Trainin...
Reactive Java (33rd Degree)
Python Programming Essentials - M25 - os and sys modules
Chapter 06 constructors and destructors
Java 8 Lambda and Streams
GCC RTL and Machine Description
Java collections concept
Basic Concepts of OOPs (Object Oriented Programming in Java)
Python Functions Tutorial | Working With Functions In Python | Python Trainin...

What's hot (20)

PPTX
OOPS Basics With Example
KEY
Object persistence
PPT
friend function(c++)
PDF
Python Programming
PDF
Perl Scripting
PPTX
Operator overloading and type conversion in cpp
PDF
Polymorphism In Java
PDF
OpenCL 3.0 Reference Guide
PPTX
Unit1 principle of programming language
PPTX
Python-DataAbstarction.pptx
PPTX
PDF
Java 8 Lambda Expressions
PPTX
Super keyword in java
PDF
Python tutorial
PPTX
PDF
Java IO
PDF
Operator overloading
PPTX
Virtual base class
PPTX
Python-Encapsulation.pptx
OOPS Basics With Example
Object persistence
friend function(c++)
Python Programming
Perl Scripting
Operator overloading and type conversion in cpp
Polymorphism In Java
OpenCL 3.0 Reference Guide
Unit1 principle of programming language
Python-DataAbstarction.pptx
Java 8 Lambda Expressions
Super keyword in java
Python tutorial
Java IO
Operator overloading
Virtual base class
Python-Encapsulation.pptx
Ad

Similar to Design Patterns in Modern C++ (20)

PPTX
Дмитрий Нестерук, Паттерны проектирования в XXI веке
PPTX
How to Adopt Modern C++17 into Your C++ Code
PPTX
How to Adopt Modern C++17 into Your C++ Code
PDF
Effective Object Oriented Design in Cpp
PPTX
From code to pattern, part one
PDF
Mastering Modern C++: C++11, C++14, C++17, C++20, C++23
PDF
Design patterns by example
PDF
Meetup C++ A brief overview of c++17
PDF
Functional Patterns for C++ Multithreading (C++ Dev Meetup Iasi)
PPTX
Design pattern and their application
PPTX
Modern C++
PPTX
C++ process new
PPTX
OOP, API Design and MVP
PDF
The Evolution of Good Code
PDF
Basic c++ 11/14 for python programmers
PPT
designpatterns_blair_upe.ppt
PDF
An Overview Of Standard C++Tr1
PDF
STL in C++
PDF
Modern C++
Дмитрий Нестерук, Паттерны проектирования в XXI веке
How to Adopt Modern C++17 into Your C++ Code
How to Adopt Modern C++17 into Your C++ Code
Effective Object Oriented Design in Cpp
From code to pattern, part one
Mastering Modern C++: C++11, C++14, C++17, C++20, C++23
Design patterns by example
Meetup C++ A brief overview of c++17
Functional Patterns for C++ Multithreading (C++ Dev Meetup Iasi)
Design pattern and their application
Modern C++
C++ process new
OOP, API Design and MVP
The Evolution of Good Code
Basic c++ 11/14 for python programmers
designpatterns_blair_upe.ppt
An Overview Of Standard C++Tr1
STL in C++
Modern C++
Ad

More from Dmitri Nesteruk (20)

PDF
Good Ideas in Programming Languages
PDF
Design Pattern Observations
PDF
CallSharp: Automatic Input/Output Matching in .NET
PPTX
C# Tricks
PPTX
Introduction to Programming Bots
PDF
Converting Managed Languages to C++
PDF
Monte Carlo C++
PDF
Tpl DataFlow
PDF
YouTrack: Not Just an Issue Tracker
PPTX
Проект X2C
PPTX
Domain Transformations
PDF
Victor CG Erofeev - Metro UI
PDF
Developer Efficiency
PPTX
Distributed Development
PDF
Dynamics CRM Data Integration
PDF
Web mining
PDF
Data mapping tutorial
PDF
Reactive Extensions
PDF
Design Patterns in .Net
PDF
Metaprogramming
Good Ideas in Programming Languages
Design Pattern Observations
CallSharp: Automatic Input/Output Matching in .NET
C# Tricks
Introduction to Programming Bots
Converting Managed Languages to C++
Monte Carlo C++
Tpl DataFlow
YouTrack: Not Just an Issue Tracker
Проект X2C
Domain Transformations
Victor CG Erofeev - Metro UI
Developer Efficiency
Distributed Development
Dynamics CRM Data Integration
Web mining
Data mapping tutorial
Reactive Extensions
Design Patterns in .Net
Metaprogramming

Recently uploaded (20)

PDF
Encapsulation_ Review paper, used for researhc scholars
PDF
Review of recent advances in non-invasive hemoglobin estimation
PDF
KodekX | Application Modernization Development
PDF
Building Integrated photovoltaic BIPV_UPV.pdf
PPT
Teaching material agriculture food technology
PPTX
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
PDF
The Rise and Fall of 3GPP – Time for a Sabbatical?
PDF
Electronic commerce courselecture one. Pdf
PPTX
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
PDF
Spectral efficient network and resource selection model in 5G networks
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
PDF
Unlocking AI with Model Context Protocol (MCP)
PPTX
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
PDF
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
PPTX
Programs and apps: productivity, graphics, security and other tools
PPTX
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
PDF
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
PDF
Network Security Unit 5.pdf for BCA BBA.
PPTX
Understanding_Digital_Forensics_Presentation.pptx
PDF
NewMind AI Weekly Chronicles - August'25 Week I
Encapsulation_ Review paper, used for researhc scholars
Review of recent advances in non-invasive hemoglobin estimation
KodekX | Application Modernization Development
Building Integrated photovoltaic BIPV_UPV.pdf
Teaching material agriculture food technology
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
The Rise and Fall of 3GPP – Time for a Sabbatical?
Electronic commerce courselecture one. Pdf
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
Spectral efficient network and resource selection model in 5G networks
Digital-Transformation-Roadmap-for-Companies.pptx
Unlocking AI with Model Context Protocol (MCP)
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
Programs and apps: productivity, graphics, security and other tools
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
Network Security Unit 5.pdf for BCA BBA.
Understanding_Digital_Forensics_Presentation.pptx
NewMind AI Weekly Chronicles - August'25 Week I

Design Patterns in Modern C++

  • 1. Design Patterns in Modern C++, Part I Dmitri Nesteruk dmitrinеstеruk@gmаil.соm @dnesteruk
  • 2. 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
  • 4. 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
  • 5. Basic Adapter class String { string s; public: String(const string &s) : s{ s } { } };
  • 6. 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; } };
  • 7. Length Proxying class String { string s; public: String(const string &s) : s{ s } { } vector<string> split(string input); size_t get_length() const { return s.length(); } };
  • 8. Length Proxying class String { string s; public: String(const string &s) : s{ s } { } vector<string> split(string input); size_t get_length() const { return s.length(); } // non-standard! __declspec(property(get = get_length)) size_t length; };
  • 9. 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;
  • 10. 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 :)
  • 12. Scenario • Neurons connect to other neurons • Neuron layers are collections of neurons • These two need to be connectable
  • 13. Scenario struct Neuron { vector<Neuron*> in, out; unsigned int id; Neuron() { static int id = 1; this->id = id++; } }
  • 14. Scenario struct NeuronLayer : vector<Neuron> { NeuronLayer(int count) { while (count-- > 0) emplace_back(Neuron{}); } }
  • 15. 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 • NeuronLayer to Neuron • NeuronLayer to NeuronLayer
  • 16. One Function Solution? • Simple: treat Neuron as NeuronLayer of size 1 • Not strictly correct • Does not take into account other concepts (e.g.,NeuronRing) • Better: expose a single Neuron in an iterable fashion • Other programming languages have interfaces for iteration • E.g., C# IEnumerable<T> • yield keyword • C++ does duck typing • Expects begin/end pair • One function solution not possible, but…
  • 17. Generic Connection Function struct Neuron { ... 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); } };
  • 18. Generic Connection Function struct NeuronLayer : vector<Neuron> { … template <typename T> void connect_to(T& other) { for (Neuron& from : *this) for (Neuron& to : other) from.connect_to(to); } };
  • 19. How to Iterate on a Single Value? struct Neuron { … Neuron* begin() { return this; } Neuron* end() { return this + 1; } };
  • 20. API Usage Neuron n, n2; NeuronLayer nl, nl2; n.connect_to(n2); n.connect_to(nl); nl.connect_to(n); nl.connect_to(nl2);
  • 22. 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
  • 23. Scenario enum class Color { Red, Green, Blue }; enum class Size { Small, Medium, Large }; struct Product { std::string name; Color color; Size size; };
  • 24. Filtering Products struct ProductFilter { typedef std::vector<Product*> Items; static Items by_color(Items items, Color color) { Items result; for (auto& i : items) if (i->color == color) result.push_back(i); return result; } }
  • 25. Filtering Products struct ProductFilter { typedef std::vector<Product*> Items; static Items by_color(Items items, Color color) { … } static Items by_size(Items items, Size size) { Items result; for (auto& i : items) if (i->size == size) result.push_back(i); return result; } }
  • 26. Filtering Products struct ProductFilter { typedef std::vector<Product*> Items; static Items by_color(Items items, Color color) { … } static Items by_size(Items items, Size size) { … } static Items by_color_and_size(Items items, Size size, Color color) { Items result; for (auto& i : items) if (i->size == size && i->color == color) result.push_back(i); return result; } }
  • 27. 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!
  • 28. ISpecification and IFilter template <typename T> struct ISpecification { virtual bool is_satisfied(T* item) = 0; }; template <typename T> struct IFilter { virtual std::vector<T*> filter( std::vector<T*> items, ISpecification<T>& spec) = 0; };
  • 29. A Better Filter struct ProductFilter : IFilter<Product> { typedef std::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; } };
  • 30. Making Specifications struct ColorSpecification : ISpecification<Product> { Color color; explicit ColorSpecification(const Color color) : color{color} { } bool is_satisfied(Product* item) override { return item->color == color; } }; // same for SizeSpecification
  • 31. 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 }; ProductFilter pf; ColorSpecification green(Color::Green); auto green_things = pf.filter(all, green); for (auto& x : green_things) std::cout << x->name << " is green" << std::endl;
  • 32. Filtering on 2..N criteria • How to filter by size and color? • We don’t want a SizeAndColorSpecification • State space explosion • Create combinators • A specification which combines two other specifications • E.g., AndSpecification
  • 33. AndSpecification Combinator template <typename T> struct AndSpecification : 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); } };
  • 34. Filtering by Size AND Color ProductFilter pf; ColorSpecification green(Color::Green); SizeSpecification big(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;
  • 35. 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
  • 37. Scenario • Consider the construction of structured data • E.g., an HTML web page • Stuctured and formalized • Rules (e.g., P cannot contain another P) • Can we provide an API for building these?
  • 38. Building a Simple HTML List // <ul><li>hello</li><li>world</li></ul> string words[] = { "hello", "world" }; ostringstream oss; oss << "<ul>"; for (auto w : words) oss << " <li>" << w << "</li>"; oss << "</ul>"; printf(oss.str().c_str());
  • 39. HtmlElement struct HtmlElement { string name; string text; vector<HtmlElement> elements; const size_t indent_size = 2; string str(int indent = 0) const; // pretty-print }
  • 40. Html Builder (non-fluent) struct HtmlBuilder { HtmlElement root; HtmlBuilder(string root_name) { root.name = root_name; } void add_child(string child_name, string child_text) { HtmlElement e{ child_name, child_text }; root.elements.emplace_back(e); } string str() { return root.str(); } }
  • 41. Html Builder (non-fluent) HtmlBuilder builder{"ul"}; builder.add_child("li", "hello") builder.add_child("li", "world"); cout << builder.str() << endl;
  • 42. Making It Fluent struct HtmlBuilder { HtmlElement root; HtmlBuilder(string root_name) { root.name = root_name; } HtmlBuilder& add_child(string child_name, string child_text) { HtmlElement e{ child_name, child_text }; root.elements.emplace_back(e); return *this; } string str() { return root.str(); } }
  • 43. Html Builder HtmlBuilder builder{"ul"}; builder.add_child("li", "hello").add_child("li", "world"); cout << builder.str() << endl;
  • 44. Associate Builder & Object Being Built struct HtmlElement { static HtmlBuilder build(string root_name) { return HtmlBuilder{root_name}; } }; // usage: HtmlElement::build("ul") .add_child_2("li", "hello").add_child_2("li", "world");
  • 45. Groovy-Style Builders • Express the structure of the HTML in code • No visible function calls • UL { LI {“hello”}, LI {“world”} } • Possible in C++ using uniform initialization
  • 46. Tag (= HTML Element) struct Tag { 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} { } }
  • 47. Paragraph struct P : Tag { explicit P(const std::string& text) : Tag{"p", text} { } P(std::initializer_list<Tag> children) : Tag("p", children) { } };
  • 48. Image struct IMG : Tag { explicit IMG(const std::string& url) : Tag{"img", ""} { attributes.emplace_back(make_pair("src", url)); } };
  • 49. Example Usage std::cout << P { IMG {"http://pokemon.com/pikachu.png"} } << std::endl;
  • 50. 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 several builders
  • 51. Personal/Work Information class Person { // address std::string street_address, post_code, city; // employment std::string company_name, position; int annual_income = 0; Person() {} // private! }
  • 52. 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 PersonAddressBuilder lives(); PersonJobBuilder works(); }
  • 53. 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 PersonAddressBuilder lives(); PersonJobBuilder works(); }
  • 54. Person Builder Facet Functions PersonAddressBuilder PersonBuilder::lives() { return PersonAddressBuilder{ person }; } PersonJobBuilder PersonBuilder::works() { return PersonJobBuilder{ person }; }
  • 55. Person Address Builder class PersonAddressBuilder : public PersonBuilder { typedef PersonAddressBuilder Self; 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); };
  • 56. Person Job Builder class PersonJobBuilder : public PersonBuilder { typedef PersonJobBuilder Self; public: explicit PersonJobBuilder(Person& person) : PersonBuilder{ person } { } Self& at(std::string company_name); Self& as_a(std::string position); Self& earning(int annual_income); };
  • 57. Back to Person class Person { // fields public: static PersonBuilder create(); friend class PersonBuilder; friend class PersonAddressBuilder; friend class PersonJobBuilder; };
  • 58. 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);
  • 60. 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
  • 61. Monads • Design patterns in functional programming • First-class function support • Related concepts • Algebraic data types • Pattern matching • Implementable to some degree in C++ • Functional objects/lambdas
  • 62. Scenario struct Address { char* house_name; // why not string? } struct Person { Address* address; }
  • 63. 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; } }
  • 64. 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
  • 65. Maybe<T> template <typename T> struct Maybe { T* context; Maybe(T *context) : context(context) { } }; // but, given Person* p, we cannot make a ‘new Maybe(p)’ template <typename T> Maybe<T> maybe(T* context) { return Maybe<T>(context); }
  • 66. Usage So Far void print_house_name(Person* p) { maybe(p). // now drill down :) }
  • 67. Maybe::With template <typename T> struct Maybe { ... template <typename TFunc> auto With(TFunc evaluator) { if (context == nullptr) return ??? // cannot return maybe(nullptr) :( return maybe(evaluator(context)); }; }
  • 68. 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< typename remove_pointer< decltype(evaluator(context)) >::type>(nullptr);
  • 69. Maybe::With Finished template <typename TFunc> auto With(TFunc evaluator) { if (context == nullptr) return Maybe<typename remove_pointer< decltype(evaluator(context))>::type>(nullptr); return maybe(evaluator(context)); };
  • 70. Usage So Far void print_house_name(Person* p) { maybe(p) // now drill down :) .With([](auto x) { return x->address; }) .With([](auto x) { return x->house_name; }) . // print here (if context is not null) }
  • 71. Maybe::Do template <typename TFunc> auto Do(TFunc action) { // if context is OK, perform action on it if (context != nullptr) action(context); // no context transition, so... return *this; }
  • 72. How It Works • print_house_name(nullptr) • Context is null from the outset and continues to be null • Nothing happens in 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
  • 73. 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’)
  • 74. That’s It! • Questions? • Design Patterns in C++ courses on Pluralsight • dmitrinеsteruk /at/ gmail.com • @dnesteruk
  • 75. Design Patterns in Modern C++, Part II Dmitri Nesteruk dmitrinеstеruk@gmаil.соm @dnesteruk
  • 76. What’s In This Talk? • Part II of my Design Patterns talks • Part I of the talk available online inEnglish and Russian • Examples of design patterns implemented in C++ • Disclaimer: C++ hasn’t internalized any patterns (yet) • Patterns • Memento • Visitor • Observer • Interpreter
  • 78. 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 { int balance{0}; public: void deposit(int x) { balance += x; } void withdraw(int x) { if (balance >= x) balance -= x; } };
  • 79. Memento (a.k.a. Token, Cookie) class Memento { int balance; public: Memento(int balance) : 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
  • 80. Deposit and Restore Memento deposit(int amount) { balance += amount; return { balance }; } void restore(const Memento& m) { balance = m.balance; } BankAccount ba{ 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
  • 81. Storing Changes class BankAccount { int balance{0}, current; vector<shared_ptr<Memento>> changes; public: BankAccount(const int balance) : balance(balance) { changes.emplace_back(make_shared<Memento>(balance)); current = 0; } };
  • 82. 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{}; }
  • 83. 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
  • 84. 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_cookie that • 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?
  • 85. Observer This has been done to death, but…
  • 86. Simple Model class Person { int age; public: void set_age(int age) { this->age = age; } int get_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!
  • 87. Person Listener struct PersonListener { virtual ~PersonListener() = default; virtual void person_changed(Person& p, const string& property_name, const any new_value) = 0; };
  • 88. 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); } };
  • 89. Setter Change void set_age(const int age) { if (this->age == age) return; this->age = age; notify("age", this->age); }
  • 90. Consumption struct ConsoleListener : PersonListener { void person_changed(Person& p, const string& property_name, const any 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}; ConsoleListener cl; p.subscribe(&cl); p.set_age(15); p.set_age(16);
  • 91. 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?
  • 92. Notifying on Dependent Property void set_age(const int age) { 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
  • 93. 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
  • 94. Thread Safety static mutex mtx; 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_lock for reading, unique_lock for writing) • Unsubscription simply nulls the listener • Must check for nullptr • Remove at the end of notify() • Alternative: use concurrent_vector • Guaranteed thread-safe addition • No easy removal
  • 95. Reentrancy struct TrafficAdministration : Observer<Person> { void field_changed(Person& source, const string& 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 
  • 96. Observer Problems • Move from mutex to recursive_mutex • Doesn’t solve all problems • See Thread-safe Observer Pattern – You’re doing it Wrong (Tony Van Eerd)
  • 97. 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 all slots • 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();
  • 98. INotifyPropertyChanged<T> template <typename T> struct INotifyPropertyChanged { virtual ~INotifyPropertyChanged() = default; signal<void(T&, const string&)> property_changed; }; struct Person : INotifyPropertyChanged<Person> { void set_age(const int age) { if (this->age == age) return; this->age = age; property_changed(*this, "age"); } };
  • 99. Consuming INPC Model Person p{19}; p.property_changed.connect( [](Person&, const string& prop_name) { cout << prop_name << " has been changed" << endl; }); p.set_age(20);
  • 100. Interpreter Make your own programming language!
  • 101. Interpreter • Interpret textual input • A branch of computer science • Single item: atoi, lexical_cast, etc. • Custom file format: XML, CSV • Embedded DSL: regex • Own programming language
  • 102. Interpreting Numeric Expressions (13-4)-(12+1) Lex: [(] [13] [-] [4] [)] [-] … Parse: Op(-, Op(-, 13, 4), Op(+,12,1))
  • 103. Token struct Token { enum Type { integer, plus, minus, lparen, rparen } type; string text; explicit Token(Type type, const string& text) : type{type}, text{text} {} };
  • 104. Lexing vector<Token> lex(const string& 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: ostringstream buffer; 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; } } …
  • 105. Parsing Structures struct Element { virtual ~Element() = default; virtual int eval() const = 0; }; struct Integer : Element { int value; explicit Integer(const int value) : value(value) { } int eval() const override { return value; } }; struct BinaryOperation : Element { enum Type { addition, subtraction } type; shared_ptr<Element> lhs, rhs; int eval() const override { if (type == addition) return lhs->eval() + rhs->eval(); return lhs->eval() - rhs->eval(); } }; shared_ptr<Element> parse(const vector<Token>& tokens) { ⋮ }
  • 106. 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; }
  • 107. Boost.Sprit • A Boost library for interpreting text • Uses a de facto DSL for defining the parser • Defining a separate lexer not mandatory • Favors Boost.Variant for polymorphic types • Structurally matches definitions to OOP structures
  • 108. Tlön Programming Language • Proof-of-concept language transpiled into C++ • Parser + pretty printer +notepadlike app • Some language features • Shorter principal integral types • Primary constructors • Tuples • Non-keyboard characters (keyboard-unfriendly)
  • 110. 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
  • 111. 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