Transcript
Page 1: Design Patterns in Modern C++

Design Patterns inModern C++, Part I

Dmitri Nesterukdmitrinеstеruk@gmаil.соm

@dnesteruk

Page 2: Design Patterns in Modern C++

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

Page 3: Design Patterns in Modern C++

Adapter

Page 4: Design Patterns in Modern C++

STL String Complaints

• Making a string is easystring s{“hello world”};

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

• Instead I would preferauto 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

Page 5: Design Patterns in Modern C++

Basic Adapter

class String {string s;

public:String(const string &s) : s{ s } { }

};

Page 6: Design Patterns in Modern C++

Implement Splitclass 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;

}};

Page 7: Design Patterns in Modern C++

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(); }

};

Page 8: Design Patterns in Modern C++

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;

};

Page 9: Design Patterns in Modern C++

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;

Page 10: Design Patterns in Modern C++

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 :)

Page 11: Design Patterns in Modern C++

Composite

Page 12: Design Patterns in Modern C++

Scenario

• Neurons connect to other neurons

• Neuron layers are collections of neurons

• These two need to be connectable

Page 13: Design Patterns in Modern C++

Scenariostruct Neuron{

vector<Neuron*> in, out;unsigned int id;

Neuron(){

static int id = 1;this->id = id++;

}}

Page 14: Design Patterns in Modern C++

Scenario

struct NeuronLayer : vector<Neuron>{NeuronLayer(int count){while (count-- > 0)emplace_back(Neuron{});

}}

Page 15: Design Patterns in Modern C++

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

Page 16: Design Patterns in Modern C++

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…

Page 17: Design Patterns in Modern C++

Generic Connection Functionstruct 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);

}};

Page 18: Design Patterns in Modern C++

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);

}};

Page 19: Design Patterns in Modern C++

How to Iterate on a Single Value?

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

};

Page 20: Design Patterns in Modern C++

API Usage

Neuron n, n2;NeuronLayer nl, nl2;

n.connect_to(n2);n.connect_to(nl);nl.connect_to(n);nl.connect_to(nl2);

Page 21: Design Patterns in Modern C++

Specification Pattern and the OCP

Page 22: Design Patterns in Modern C++

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

Page 23: Design Patterns in Modern C++

Scenario

enum class Color { Red, Green, Blue };enum class Size { Small, Medium, Large };

struct Product{std::string name;Color color;Size size;

};

Page 24: Design Patterns in Modern C++

Filtering Productsstruct 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;}

}

Page 25: Design Patterns in Modern C++

Filtering Productsstruct 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;}

}

Page 26: Design Patterns in Modern C++

Filtering Productsstruct 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;}

}

Page 27: Design Patterns in Modern C++

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!

Page 28: Design Patterns in Modern C++

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;

};

Page 29: Design Patterns in Modern C++

A Better Filterstruct 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;}

};

Page 30: Design Patterns in Modern C++

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

Page 31: Design Patterns in Modern C++

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;

Page 32: Design Patterns in Modern C++

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

Page 33: Design Patterns in Modern C++

AndSpecification Combinatortemplate <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);}

};

Page 34: Design Patterns in Modern C++

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;

Page 35: Design Patterns in Modern C++

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

Page 36: Design Patterns in Modern C++

Fluent and Groovy-Style Builders

Page 37: Design Patterns in Modern C++

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?

Page 38: Design Patterns in Modern C++

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());

Page 39: Design Patterns in Modern C++

HtmlElement

struct HtmlElement{

string name;string text;vector<HtmlElement> elements;const size_t indent_size = 2;

string str(int indent = 0) const; // pretty-print}

Page 40: Design Patterns in Modern C++

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(); }

}

Page 41: Design Patterns in Modern C++

Html Builder (non-fluent)

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

Page 42: Design Patterns in Modern C++

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(); }

}

Page 43: Design Patterns in Modern C++

Html Builder

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

Page 44: Design Patterns in Modern C++

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");

Page 45: Design Patterns in Modern C++

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

Page 46: Design Patterns in Modern C++

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} { }}

Page 47: Design Patterns in Modern C++

Paragraph

struct P : Tag{

explicit P(const std::string& text): Tag{"p", text}

{}P(std::initializer_list<Tag> children): Tag("p", children)

{}

};

Page 48: Design Patterns in Modern C++

Image

struct IMG : Tag{

explicit IMG(const std::string& url): Tag{"img", ""}

{attributes.emplace_back(make_pair("src", url));

}};

Page 49: Design Patterns in Modern C++

Example Usage

std::cout <<

P {IMG {"http://pokemon.com/pikachu.png"}

}

<< std::endl;

Page 50: Design Patterns in Modern C++

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

Page 51: Design Patterns in Modern C++

Personal/Work Information

class Person{

// addressstd::string street_address, post_code, city;

// employmentstd::string company_name, position;int annual_income = 0;

Person() {} // private!}

Page 52: Design Patterns in Modern C++

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 facetsPersonAddressBuilder lives();PersonJobBuilder works();

}

Page 53: Design Patterns in Modern C++

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 facetsPersonAddressBuilder lives();PersonJobBuilder works();

}

Page 54: Design Patterns in Modern C++

Person Builder Facet Functions

PersonAddressBuilder PersonBuilder::lives(){

return PersonAddressBuilder{ person };}

PersonJobBuilder PersonBuilder::works(){

return PersonJobBuilder{ person };}

Page 55: Design Patterns in Modern C++

Person Address Builderclass 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);

};

Page 56: Design Patterns in Modern C++

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);

};

Page 57: Design Patterns in Modern C++

Back to Person

class Person{

// fieldspublic:

static PersonBuilder create();

friend class PersonBuilder;friend class PersonAddressBuilder;friend class PersonJobBuilder;

};

Page 58: Design Patterns in Modern C++

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);

Page 59: Design Patterns in Modern C++

Maybe Monad

Page 60: Design Patterns in Modern C++

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

Page 61: Design Patterns in Modern C++

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

Page 62: Design Patterns in Modern C++

Scenario

struct Address{char* house_name; // why not string?

}

struct Person{Address* address;

}

Page 63: Design Patterns in Modern C++

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;

}}

Page 64: Design Patterns in Modern C++

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

Page 65: Design Patterns in Modern C++

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);}

Page 66: Design Patterns in Modern C++

Usage So Far

void print_house_name(Person* p){maybe(p). // now drill down :)

}

Page 67: Design Patterns in Modern C++

Maybe::Withtemplate <typename T> struct Maybe{

...template <typename TFunc>auto With(TFunc evaluator){

if (context == nullptr)return ??? // cannot return maybe(nullptr) :(

return maybe(evaluator(context));};

}

Page 68: Design Patterns in Modern C++

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);

Page 69: Design Patterns in Modern C++

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));

};

Page 70: Design Patterns in Modern C++

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)

}

Page 71: Design Patterns in Modern C++

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;

}

Page 72: Design Patterns in Modern C++

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

Page 73: Design Patterns in Modern C++

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 ofx => f(x) as in C#

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

Page 74: Design Patterns in Modern C++

That’s It!

• Questions?• Design Patterns in C++ courses on Pluralsight• dmitrinеsteruk /at/ gmail.com• @dnesteruk

Page 75: Design Patterns in Modern C++

Design Patterns in Modern C++, Part II

Dmitri Nesterukdmitrinеstеruk@gmаil.соm

@dnesteruk

Page 76: Design Patterns in Modern C++

What’s In This Talk?

• Part II of my Design Patterns talks• Part I of the talk available online in English and Russian

• Examples of design patterns implemented in C++• Disclaimer: C++ hasn’t internalized any patterns (yet)

• Patterns• Memento• Visitor• Observer• Interpreter

Page 77: Design Patterns in Modern C++

MementoHelping implement undo/redo

Page 78: Design Patterns in Modern C++

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;

}};

Page 79: Design Patterns in Modern C++

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

Page 80: Design Patterns in Modern C++

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); // 150auto m2 = ba.deposit(25); // 175// undo to m1ba.restore(m1); // 150// redoba.restore(m2); // 175

Page 81: Design Patterns in Modern C++

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;

}};

Page 82: Design Patterns in Modern C++

Undo and Redo

shared_ptr<Memento> deposit(intamount){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{};

}

Page 83: Design Patterns in Modern C++

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

Page 84: Design Patterns in Modern C++

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?

Page 85: Design Patterns in Modern C++

ObserverThis has been done to death, but…

Page 86: Design Patterns in Modern C++

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 informedwhenever age changes

• Need to modify the setter!

Page 87: Design Patterns in Modern C++

Person Listener

struct PersonListener{virtual ~PersonListener() = default;virtual void person_changed(Person& p,

const string& property_name, const any new_value) = 0;};

Page 88: Design Patterns in Modern C++

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);

}};

Page 89: Design Patterns in Modern C++

Setter Change

void set_age(const int age){if (this->age == age) return;this->age = age;notify("age", this->age);

}

Page 90: Design Patterns in Modern C++

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);

Page 91: Design Patterns in Modern C++

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?

Page 92: Design Patterns in Modern C++

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

Page 93: Design Patterns in Modern C++

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

Page 94: Design Patterns in Modern C++

Thread Safetystatic 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

Page 95: Design Patterns in Modern C++

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 anymorecout << "We no longer care!\n";source.unsubscribe(this);

}}}};

Age changes (1617):• notify() called• Lock taken

field_changed• unsubscribe()

unsubscribe()• Tries to take a lock• But it’s already taken

Page 96: Design Patterns in Modern C++

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)

Page 97: Design Patterns in Modern C++

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 viablock.unblock();

Page 98: Design Patterns in Modern C++

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");}

};

Page 99: Design Patterns in Modern C++

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);

Page 100: Design Patterns in Modern C++

InterpreterMake your own programming language!

Page 101: Design Patterns in Modern C++

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

Page 102: Design Patterns in Modern C++

Interpreting Numeric Expressions

(13-4)-(12+1)

Lex: [(] [13] [-] [4] [)] [-] …

Parse: Op(-, Op(-, 13, 4), Op(+,12,1))

Page 103: Design Patterns in Modern C++

Token

struct Token{enum Type { integer, plus, minus, lparen, rparen

} type;

string text;

explicit Token(Type type, const string& text) :type{type}, text{text} {}

};

Page 104: Design Patterns in Modern C++

Lexingvector<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;}

}…

Page 105: Design Patterns in Modern C++

Parsing Structuresstruct 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){⋮

}

Page 106: Design Patterns in Modern C++

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;

}

Page 107: Design Patterns in Modern C++

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

Page 108: Design Patterns in Modern C++

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)

Page 109: Design Patterns in Modern C++

Visitor

Page 110: Design Patterns in Modern C++

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

Page 111: Design Patterns in Modern C++

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


Top Related