Design Patterns in Modern C++

Download Design Patterns in Modern C++

Post on 12-Apr-2017




38 download


  • Design Patterns inModern C++, Part I

    Dmitri Nesterukdmitrinstruk@gmil.m


  • Whats 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 easystring s{hello world};

    Getting its constituent parts is notvector 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

  • Basic Adapter

    class String {string s;

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


  • Implement Splitclass String {

    string s;public:

    String(const string &s) : s{ s } { }vector split(string input){

    vector 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 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 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

  • 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 dont need Add your own features :)

  • Composite

  • Scenario

    Neurons connect to other neurons

    Neuron layers are collections of neurons

    These two need to be connectable

  • Scenariostruct Neuron{

    vector in, out;unsigned int id;


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


  • Scenario

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


  • State Space Explosition

    void connect_to(Neuron& other){out.push_back(&other);;

    } 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 yield keyword

    C++ does duck typing Expects begin/end pair

    One function solution not possible, but

  • Generic Connection Functionstruct Neuron{

    ...template void connect_to(T& other){

    for (Neuron& to : other)connect_to(to);

    }template void connect_to(Neuron& other){



  • Generic Connection Function

    struct NeuronLayer : vector{template 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;


  • 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 Productsstruct ProductFilter{

    typedef std::vector 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 Productsstruct ProductFilter{

    typedef std::vector 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 Productsstruct ProductFilter{

    typedef std::vector 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 struct ISpecification{virtual bool is_satisfied(T* item) = 0;

    };template struct IFilter{virtual std::vector filter(std::vector items, ISpecification& spec) = 0;


  • A Better Filterstruct ProductFilter : IFilter{

    typedef std::vector Products;Products filter(

    Products items, ISpecification& spec) override

    {Products result;for (auto& p : items)

    if (spec.is_satisfied(p))result.push_back(p);

    return result;}


  • Making Specifications

    struct ColorSpecification : ISpecification{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 all{ &apple, &tree, &house };

    ProductFilter pf;ColorSpecification green(Color::Green);

    auto green_things = pf.filter(all, green);for (auto& x : green_things)std::cout name

  • Filtering on 2..N criteria

    How to filter by size and color? We dont want a SizeAndColorSpecification

    State space explosion Create combinators

    A specification which combines two other specifications E.g., AndSpecification

  • AndSpecification Combinatortemplate struct AndSpecification : ISpecification{

    ISpecification& first;ISpecification& second;

    AndSpecification(ISpecification& first, ISpecification& 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 green_and_big{ big, green };

    auto big_green_things = pf.filter(all, green_and_big);for (auto& x : big_green_things)

    std::cout name

  • 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

    // helloworldstring words[] = { "hello", "world" };ostringstream oss;oss

  • HtmlElement

    struct HtmlElement{

    string name;string text;vector 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; }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

  • Making It Fluent

    struct HtmlBuilder{

    HtmlElement root;HtmlBuilder(string 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

  • 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 children;vector attributes;

    protected:Tag(const std::string& name, const std::string& text)

    : name{name}, text{text} { }Tag(const std::string& name, const std::vector& children)

    : name{name}, children{children} { }}

  • Paragraph

    struct P : Tag{

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

    {}P(std::initializer_list 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


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

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

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


  • Person Builder Facet Functions

    PersonAddressBuilder PersonBuilder::lives(){

    return PersonAddressBuilder{ person };}

    PersonJobBuilder PersonBuilder::works(){

    return PersonJobBuilder{ person };}

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


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

    // fieldspublic:

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




  • 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 Idiomatic


  • 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 address->house_name

  • Maybe Monad

    Encapsulate the drill down aspect of code Construct a Maybe 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

  • Maybetemplate struct Maybe {

    T* context;Maybe(T *context) : context(context) { }


    // but, given Person* p, we cannot make a new Maybe(p)

    template Maybe maybe(T* context){

    return Maybe(context);}

  • Usage So Far

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


  • Maybe::Withtemplate struct Maybe{

    ...template 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 But the type of U should be the return type of evaluator But evaluator returns U* and we need U Therefore return Maybe(nullptr);

  • Maybe::With Finished

    template auto With(TFunc evaluator){

    if (context == nullptr)return Maybe::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 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 ofx => f(x) as in C#

    No implicits (e.g. Kotlins it)

  • Thats It!

    Questions? Design Patterns in C++ courses on Pluralsight dmitrinsteruk /at/ @dnesteruk

  • Design Patterns in Modern C++, Part II

    Dmitri Nesterukdmitrinstruk@gmil.m


  • Whats 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++ hasnt internalized any patterns (yet)

    Patterns Memento Visitor Observer Interpreter

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

  • Storing Changes

    class BankAccount {int balance{0}, current;vector changes;

    public:BankAccount(const int balance) : balance(balance){

    changes.emplace_back(make_shared(balance));current = 0;


  • Undo and Redo

    shared_ptr deposit(intamount){balance += amount;auto m = make_shared(balance);

    changes.push_back(m);++current;return m;


    shared_ptr undo(){if (current > 0){--current;auto m = changes[current];balance = m->balance;return m;



  • 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 doesnt push_back() return a reference?

    Well, it could, but As long as youre not resizing due

    to addition How to refer to an element of

    vector? Dangerous unless append-only Change to vector< shared_ptr> Change to list Some range-tracking magic token


    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?

  • ObserverThis 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 informedwhenever 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 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


  • 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


  • 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 Safetystatic mutex mtx;class Person {void subscribe(PersonListener* pl){lock_guard guard{mtx};

    }void unsubscribe(PersonListener* pl){lock_guard 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{void field_changed(Person& source,

    const string& field_name){

    if (field_name == "age"){if (source.get_age() < 17)


  • Observer Problems

    Move from mutex to recursive_mutex Doesnt solve all problems See Thread-safe Observer Pattern Youre doing it Wrong (Tony Van


  • Boost.Signals2 signal

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

    creates a signal auto c = s.connect([](){


  • INotifyPropertyChanged

    template struct INotifyPropertyChanged{virtual ~INotifyPropertyChanged() = default;signal property_changed;

    };struct Person : INotifyPropertyChanged{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){


  • InterpreterMake 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


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


  • Lexingvector lex(const string& input){

    vector 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

  • 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 lhs, rhs;

    int eval() const override{if (type == addition) return lhs->eval() + rhs->eval();

    return lhs->eval() - rhs->eval();}

    };shared_ptr parse(const vector& tokens){


  • Parsing Numeric Expressions

    string input{ "(13-4)-(12+1)" };auto tokens = lex(input);try {auto parsed = parse(tokens);cout

  • 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

  • Tln 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

  • Thats It!

    Design Patterns in C++ courses on Pluralsight Leanpub book (work in progress!) Tln Programming Language dmitrinsteruk /at/ @dnesteruk

    Design Patterns inModern C++, Part IWhats In This Talk?AdapterSTL String ComplaintsBasic AdapterImplement SplitLength ProxyingLength ProxyingString Wrapper UsageAdapter SummaryCompositeScenarioScenarioScenarioState Space ExplositionOne Function Solution?Generic Connection FunctionGeneric Connection FunctionHow to Iterate on a Single Value?API UsageSpecification Pattern and the OCPOpen-Closed PrincipleScenarioFiltering ProductsFiltering ProductsFiltering ProductsViolating OCPISpecification and IFilterA Better FilterMaking SpecificationsImproved Filter UsageFiltering on 2..N criteriaAndSpecification CombinatorFiltering by Size AND ColorSpecification SummaryFluent and Groovy-Style BuildersScenarioBuilding a Simple HTML ListHtmlElementHtml Builder (non-fluent)Html Builder (non-fluent)Making It FluentHtml BuilderAssociate Builder & Object Being BuiltGroovy-Style BuildersTag (= HTML Element)ParagraphImageExample UsageFacet BuildersPersonal/Work InformationPerson Builder (Exposes Facet Builders)Person Builder (Exposes Facet Builders)Person Builder Facet FunctionsPerson Address BuilderPerson Job BuilderBack to PersonFinal Person Builder UsageMaybe MonadPresence or AbsenceMonadsScenarioPrint House Name, If AnyMaybe MonadMaybeUsage So FarMaybe::WithWhat is ???Maybe::With FinishedUsage So FarMaybe::DoHow It WorksMaybe Monad SummaryThats It!Design Patterns in Modern C++, Part IIWhats In This Talk?MementoBank AccountMemento (a.k.a. Token, Cookie)Deposit and RestoreStoring ChangesUndo and RedoUndo/Redo ProblemsCookie Monster!ObserverSimple ModelPerson ListenerPerson ImplementationSetter ChangeConsumptionDependent PropertyNotifying on Dependent PropertyObserver ProblemsThread SafetyReentrancyObserver ProblemsBoost.Signals2INotifyPropertyChangedConsuming INPC ModelInterpreterInterpreterInterpreting Numeric ExpressionsTokenLexingParsing StructuresParsing Numeric ExpressionsBoost.SpritTln Programming LanguageVisitorAdding Side Functionality to ClassesThats It!