Bringing Business Logic Back to C++

by Borislav Stanimirov / @stanimirovb

Bringing Business Logic Back to B++

by Borislav Stanimirov / @stanimirovb

Bringing Business Logic Back to C++

by Borislav Stanimirov / @stanimirovb

Hello, World



#include <iostream>

int main()
{
    std::cout << "Hi, I'm Borislav!\n";
    std::cout << "These slides are here: https://is.gd/bbb2cpp\n";
    return 0;
}

Hello, World



#include <iostream>

int main()
{
    std::cout << "Hi, I'm Borislav!\n";
    std::cout << "These slides are here: https://is.gd/bbb2cpp\n";
    return 0;
}

Bulgaria

Borislav Stanimirov


  • Mostly a C++ programmer
  • Mostly a game programmer
  • Open-source programmer
  • github.com/iboB

What is in this talk?

What is in this talk?


  • State of the world
  • Various existing developments and techniques
  • Some emerging ones
  • The future?

What is Business Logic?

A part of a program which deals with the real world rules that determine how data is obtained, stored and processed.

The part which deals with the software's purpose


                    int a;
                    int b;
                    cin >> a >> b;
                    cout << a + b << '\n';
                

Business logic: adding two integers

The part which deals with the software's purpose


                    int a;
                    int b;
                    cin >> a >> b;
                    cout << a + b << '\n';
                

Business logic: adding two integers

The part which deals with the software's purpose


                    FirstInteger a;
                    SecondInteger b;
                    input.obtainValues(a, b);
                    gui.display(a + b);
                

Business logic: adding two integers

The part which deals with the software's purpose


                    FirstInteger a;
                    SecondInteger b;
                    input.obtainValues(a, b);
                    gui.display(a + b);
                

Business logic: adding two integers

Presentation, i/o...


                    FirstInteger a;
                    SecondInteger b;
                    input.obtainValues(a, b);
                    gui.display(a + b);
                

Non-business logic

Complex software doesn't mean complex business logic.

_

Simple. Right?

What do we have here?


  • Websites - JS, C#, Java, Ruby...
  • Gameplay - lua, C#, Python...
  • CAD - Python, C#...
  • Enterprise - Everything but C++



People don't seem to want to write business logic in C++



Note that there still is a lot of underlying C++ in such projects

Well... is there a problem with that?

Problems with that


  • The code is slower
  • There is more complexity in the binding layer
  • There are duplicated functionalities (which means duplicated bugs)

Why don't people use C++?

1. OOP

Controversial. I know

Criticism of OOP


  • You want a banana, but with it you also get the gorilla and the entire jungle
  • It dangerously couples the data with the functionality
  • It's not reusable
  • It's slow


People forget that C++ is an OOP language

Defense of OOP


  • Many cricisims target concrete implementations
  • Many are about misuse of OOP
  • Some are about misconseptions of OOP



But most importantly...

Software is written by human beings

Human beings think in terms of objects

Which languages thrive in fields with heavy business logic?


Almost all are object-oriented ones.

Powerful OOP


    function f(shape) {
        // Everything that has a draw method works
        shape.draw();
    }
    Square = function () {
        this.draw = function() {
            console.log("Square");
        }
    };
    Circle = function () {
        this.draw = function() {
            console.log("Circle");
        }
    };
    f(new Square);
    f(new Circle);
            

Vanilla C++ OOP


    struct Shape {
        virtual void draw(ostream& out) const = 0;
    }
    void f(const Shape& s) {
        s.draw(cout);
    }
    struct Square : public Shape {
        virtual void draw(ostream& out) const override { out << "Square\n"; }
    };
    struct Circle : public Shape {
        virtual void draw(ostream& out) const override { out << "Circle\n"; }
    };
    int main() {
        f(Square{});
        f(Circle{});
    }
            

OOP isn't modern C++

However C++ is a pretty powerful language.

Polymorphic type-erasure wrappers

Boost.TypeErasure, Dyno, Folly.Poly, [Boost].TE


using Shape = Library_Magic(void, draw, (ostream&));
void f(const Shape& s) {
    s.draw(cout);
}
struct Square {
    void draw(ostream& out) const { out << "Square\n"; }
};
struct Circle {
    void draw(ostream& out) const { out << "Circle\n"; }
};
int main() {
    f(Square{});
    f(Circle{});
}

Polymorphic type-erasure wrappers

Boost.TypeErasure, Dyno, Folly.Poly, [Boost].TE


using Shape = Library_Magic(void, draw, (ostream&));
void f(const Shape& s) {
    s.draw(cout);
}
struct Square {
    void draw(ostream& out) const { out << "Square\n"; }
};
struct Circle {
    void draw(ostream& out) const { out << "Circle\n"; }
};
int main() {
    f(Square{});
    f(Circle{});
}

Powerful OOP


    Square.ptototype.area = function() {
        return this.side * this.side;
    }

    Circle.ptototype.area = function() {
        return this.radius * this.radius * Math.PI;
    }

    s = new Square;
    s.side = 5;
    console.log(s.area()); // 25
            

Vanilla C++ OOP


    struct Shape {
        virtual void draw(ostream& out) const = 0;
    }
    struct Square : public Shape {
        virtual void draw(ostream& out) const override { out << "Square\n"; }
        int side;
    };
    int main()
    {
        shared_ptr<Shape> ptr = make_shared<Square>();
        cout << ptr->area() << '\n'; // ??
    }
            

Vanilla C++ OOP


    struct Shape {
        virtual void draw(ostream& out) const = 0;
        virtual double area() const = 0;
    }
    struct Square : public Shape {
        virtual void draw(ostream& out) const override { out << "Square\n"; }
        virtual double area() const override { return side * side; }
        double side;
    };
    int main()
    {
        shared_ptr<Shape> ptr = make_shared<Square>();
        cout << ptr->area() << '\n';
    }
            

And what if we can't just edit the classes?

Open methods

Not to be confused with extension methods or UCS

[Boost].TE, yomm2


declare_method(double, area, (virtual_<const Shape&> s);

define_method(double, area, (const Square& s)
{
    return s.area * s.area;
}

int main() {
    shared_ptr<Shape> ptr = make_shared<Square>();
    cout << area(ptr) << '\n';
}

Open methods

Not to be confused with extension methods or UCS

[Boost].TE, yomm2


declare_method(double, area, (virtual_<const Shape&> s);

define_method(double, area, (const Square& s)
{
    return s.area * s.area;
}

int main() {
    shared_ptr<Shape> ptr = make_shared<Square>();
    cout << area(ptr) << '\n';
}

Powerful OOP


    multi sub collide(Square $s, Square $c) {
        collide_square_square($s, $c);
    }
    multi sub collide(Circle $s, Circle $c) {
        collide_circle_circle($s, $c);
    }
    multi sub collide(Square $s, Circle $c) {
        collide_square_circle($s, $c);
    }
    multi sub collide(Circle $c, Square $s) {
        collide_square_circle($s, $c);
    }

    my $s1 = get_random_shape;
    my $s2 = get_random_shape;
    say "Collision: " ~ collide($s1, $s2);
            

Vanilla C++ OOP


    struct Shape {
        virtual bool collide(const Shape* other) const = 0;
        virtual bool collideImpl(const Square*) const = 0;
        virtual bool collideImpl(const Circle*) const = 0;
        virtual bool collideImpl(const Triangle*) const = 0;
    }
    struct Square : public Shape {
        virtual bool collide(const Shape* other) const override {
            return other->collideImpl(this);
        }
        virtual bool collideImpl(const Square*) const override;
        virtual bool collideImpl(const Circle*) const override;
        virtual bool collideImpl(const Triangle*) const override;
    };
    // ...
    shared_ptr<Shape> s1 = get_random_shape();
    shared_ptr<Shape> s2 = get_random_shape();
    cout << "Collision: " << s1->collide(s2.get()) << '\n';
            

Vanilla C++ OOP


    struct Shape {
        virtual bool collide(const Shape* other) const = 0;
        virtual bool collideImpl(const Square*) const = 0;
        virtual bool collideImpl(const Circle*) const = 0;
        virtual bool collideImpl(const Triangle*) const = 0;
    }
    struct Square : public Shape {
        virtual bool collide(const Shape* other) const override {
            return other->collideImpl(this); /* copy this in every shape */
        }
        virtual bool collideImpl(const Square*) const override;
        virtual bool collideImpl(const Circle*) const override;
        virtual bool collideImpl(const Triangle*) const override;
    };
    // ...
    shared_ptr<Shape> s1 = get_random_shape();
    shared_ptr<Shape> s2 = get_random_shape();
    cout << "Collision: " << s1->collide(s2.get()) << '\n';
            

Vanilla C++ OOP


    struct Shape {
        virtual bool collide(const Shape* other) const = 0;
        virtual bool collideImpl(const Square*) const = 0;
        virtual bool collideImpl(const Circle*) const = 0;
        virtual bool collideImpl(const Triangle*) const = 0;
    }
    struct Square : public Shape {
        virtual bool collide(const Shape* other) const override {
            return other->collideImpl(this);
        }
        virtual bool collideImpl(const Square*) const override;
        virtual bool collideImpl(const Circle*) const override;
        virtual bool collideImpl(const Triangle*) const override;
    };
    // ...
    shared_ptr<Shape> s1 = get_random_shape();
    shared_ptr<Shape> s2 = get_random_shape();
    cout << "Collision: " << s1->collide(s2.get()) << '\n';
            

Multiple dispatch (Multimethods)

yomm2, Folly.Poly


declare_method(bool, collide,
    (virtual_<const Shape&> s1, (virtual_<const Shape&> s2));

define_method(bool, collide, (const Square& s, const Circle& c)
{
    return collide_square_circle(s, c);
}
define_method(bool, collide, (const Circle& c, const Square& s)
{
    return collide_square_circle(s, c);
}
// ...
shared_ptr<Shape> s1 = get_random_shape();
shared_ptr<Shape> s2 = get_random_shape();
cout << "Collision: " << collide(*s1, *s2) << '\n';

Powerful OOP

module FlyingCreature
  def move_to(target)
    puts can_move_to?(target) ?
      "flying to #{target}"
      : "can't fly to #{target}"
  end
  def can_move_to?(target)
    true # flying creatures don't care
  end
end
module AfraidOfEvens
  def can_move_to?(target)
    target % 2 != 0
  end
end
a = Object.new
a.extend(FlyingCreature)
a.move_to(10) # -> flying to 10
a.extend(AfraidOfEvens)
a.move_to(10) # -> can’t fly to 10

Powerful OOP

module FlyingCreature
  def move_to(target)
    puts can_move_to?(target) ?
      "flying to #{target}"
      : "can't fly to #{target}"
  end
  def can_move_to?(target)
    true # flying creatures don't care
  end
end
module AfraidOfEvens
  def can_move_to?(target)
    target % 2 != 0
  end
end
a = Object.new
a.extend(FlyingCreature)
a.move_to(10) # -> flying to 10
a.extend(AfraidOfEvens)
a.move_to(10) # -> can’t fly to 10

Powerful OOP

module FlyingCreature
  def move_to(target)
    puts can_move_to?(target) ?
      "flying to #{target}"
      : "can't fly to #{target}"
  end
  def can_move_to?(target)
    true # flying creatures don't care
  end
end
module AfraidOfEvens
  def can_move_to?(target)
    target % 2 != 0
  end
end
a = Object.new
a.extend(FlyingCreature)
a.move_to(10) # -> flying to 10
a.extend(AfraidOfEvens)
a.move_to(10) # -> can’t fly to 10

Powerful OOP

module FlyingCreature
  def move_to(target)
    puts can_move_to?(target) ?
      "flying to #{target}"
      : "can't fly to #{target}"
  end
  def can_move_to?(target)
    true # flying creatures don't care
  end
end
module AfraidOfEvens
  def can_move_to?(target)
    target % 2 != 0
  end
end
a = Object.new
a.extend(FlyingCreature)
a.move_to(10) # -> flying to 10
a.extend(AfraidOfEvens)
a.move_to(10) # -> can’t fly to 10

Powerful OOP

module FlyingCreature
  def move_to(target)
    puts can_move_to?(target) ?
      "flying to #{target}"
      : "can't fly to #{target}"
  end
  def can_move_to?(target)
    true # flying creatures don't care
  end
end
module AfraidOfEvens
  def can_move_to?(target)
    target % 2 != 0
  end
end
a = Object.new
a.extend(FlyingCreature)
a.move_to(10) # -> flying to 10
a.extend(AfraidOfEvens)
a.move_to(10) # -> can’t fly to 10

Powerful OOP

module FlyingCreature
  def move_to(target)
    puts can_move_to?(target) ?
      "flying to #{target}"
      : "can't fly to #{target}"
  end
  def can_move_to?(target)
    true # flying creatures don't care
  end
end
module AfraidOfEvens
  def can_move_to?(target)
    target % 2 != 0
  end
end
a = Object.new
a.extend(FlyingCreature)
a.move_to(10) # -> flying to 10
a.extend(AfraidOfEvens)
a.move_to(10) # -> can’t fly to 10

Vanilla C++ OOP


            // ???
            

Vanilla C++ OOP

struct Object
{
    shared_ptr<ICanMoveTo> m_canMoveTo;
    shared_ptr<IMoveTo> m_moveTo;
    // ... every possible method ever
    bool canMoveTo(int target) const {
        return m_canMoveTo->call(target);
    } // ... every possible method ever (again)

    void extend(shared_ptr<Creature> c) {
        c->setObject(this);
        m_canMoveTo = c;
        m_moveTo = c;
    }
    void extend(shared_ptr<ICanMoveTo> cmt) {
        cmt->setObject(this);
        m_canMoveTo = cmt;
    } // ... every possible extension ever
};
            

Vanilla C++ OOP


// ... components
// ... virtual inheritence
// ... A LOT OF CODE
Object o;
o.extend(make_shared<FlyingCreature>());
o.moveTo(10); // flying to 10
o.extend(make_shared<AfraidOfEvens>());
o.moveTo(10); // can't fly to 10
            

Dynamic Mixins

DynaMix

    DYNAMIX_MESSAGE_1(void, moveTo, int, target);
    DYNAMIX_CONST_MESSAGE_1(bool, canMoveTo, int, target);
    struct FlyingCreature {
        void moveTo(int target) {
            cout << (::canMoveTo(dm_this, target) ?
                "flying to " : "can't fly to ")
                << target << '\n';
        }
        bool canMoveTo(int target) const {
            return true;
        }
    }; DYNAMIX_DEFINE_MIXIN(FlyingCreature, moveTo_msg & canMoveTo_msg);
    struct AfraidOfEvens {
        bool canMoveTo(int target) const {
            return target % 2 != 0;
        }
    }; DYNAMIX_DEFINE_MIXIN(AfraidOfEvens, priority(1, canMoveTo_msg));
    int main() {
        object o;
        mutate(o).add<FlyingCreature>();
        moveTo(o, 10); // flying to 10
        mutate(o).add<AfraidOfEvens>();
        moveTo(o, 10); // can't fly to 10
    }
            

Dynamic Mixins

DynaMix

    DYNAMIX_MESSAGE_1(void, moveTo, int, target);
    DYNAMIX_CONST_MESSAGE_1(bool, canMoveTo, int, target);
    struct FlyingCreature {
        void moveTo(int target) {
            cout << (::canMoveTo(dm_this, target) ?
                "flying to " : "can't fly to ")
                << target << '\n';
        }
        bool canMoveTo(int target) const {
            return true;
        }
    }; DYNAMIX_DEFINE_MIXIN(FlyingCreature, moveTo_msg & canMoveTo_msg);
    struct AfraidOfEvens {
        bool canMoveTo(int target) const {
            return target % 2 != 0;
        }
    }; DYNAMIX_DEFINE_MIXIN(AfraidOfEvens, priority(1, canMoveTo_msg));
    int main() {
        object o;
        mutate(o).add<FlyingCreature>();
        moveTo(o, 10); // flying to 10
        mutate(o).add<AfraidOfEvens>();
        moveTo(o, 10); // can't fly to 10
    }
            

Dynamic Mixins

DynaMix

    DYNAMIX_MESSAGE_1(void, moveTo, int, target);
    DYNAMIX_CONST_MESSAGE_1(bool, canMoveTo, int, target);
    struct FlyingCreature {
        void moveTo(int target) {
            cout << (::canMoveTo(dm_this, target) ?
                "flying to " : "can't fly to ")
                << target << '\n';
        }
        bool canMoveTo(int target) const {
            return true;
        }
    }; DYNAMIX_DEFINE_MIXIN(FlyingCreature, moveTo_msg & canMoveTo_msg);
    struct AfraidOfEvens {
        bool canMoveTo(int target) const {
            return target % 2 != 0;
        }
    }; DYNAMIX_DEFINE_MIXIN(AfraidOfEvens, priority(1, canMoveTo_msg));
    int main() {
        object o;
        mutate(o).add<FlyingCreature>();
        moveTo(o, 10); // flying to 10
        mutate(o).add<AfraidOfEvens>();
        moveTo(o, 10); // can't fly to 10
    }
            

Dynamic Mixins

DynaMix

    DYNAMIX_MESSAGE_1(void, moveTo, int, target);
    DYNAMIX_CONST_MESSAGE_1(bool, canMoveTo, int, target);
    struct FlyingCreature {
        void moveTo(int target) {
            cout << (::canMoveTo(dm_this, target) ?
                "flying to " : "can't fly to ")
                << target << '\n';
        }
        bool canMoveTo(int target) const {
            return true;
        }
    }; DYNAMIX_DEFINE_MIXIN(FlyingCreature, moveTo_msg & canMoveTo_msg);
    struct AfraidOfEvens {
        bool canMoveTo(int target) const {
            return target % 2 != 0;
        }
    }; DYNAMIX_DEFINE_MIXIN(AfraidOfEvens, priority(1, canMoveTo_msg));
    int main() {
        object o;
        mutate(o).add<FlyingCreature>();
        moveTo(o, 10); // flying to 10
        mutate(o).add<AfraidOfEvens>();
        moveTo(o, 10); // can't fly to 10
    }
            

Dynamic Mixins

DynaMix

    DYNAMIX_MESSAGE_1(void, moveTo, int, target);
    DYNAMIX_CONST_MESSAGE_1(bool, canMoveTo, int, target);
    struct FlyingCreature {
        void moveTo(int target) {
            cout << (::canMoveTo(dm_this, target) ?
                "flying to " : "can't fly to ")
                << target << '\n';
        }
        bool canMoveTo(int target) const {
            return true;
        }
    }; DYNAMIX_DEFINE_MIXIN(FlyingCreature, moveTo_msg & canMoveTo_msg);
    struct AfraidOfEvens {
        bool canMoveTo(int target) const {
            return target % 2 != 0;
        }
    }; DYNAMIX_DEFINE_MIXIN(AfraidOfEvens, priority(1, canMoveTo_msg));
    int main() {
        object o;
        mutate(o).add<FlyingCreature>();
        moveTo(o, 10); // flying to 10
        mutate(o).add<AfraidOfEvens>();
        moveTo(o, 10); // can't fly to 10
    }
            

Dynamic Mixins

DynaMix

    DYNAMIX_MESSAGE_1(void, moveTo, int, target);
    DYNAMIX_CONST_MESSAGE_1(bool, canMoveTo, int, target);
    struct FlyingCreature {
        void moveTo(int target) {
            cout << (::canMoveTo(dm_this, target) ?
                "flying to " : "can't fly to ")
                << target << '\n';
        }
        bool canMoveTo(int target) const {
            return true;
        }
    }; DYNAMIX_DEFINE_MIXIN(FlyingCreature, moveTo_msg & canMoveTo_msg);
    struct AfraidOfEvens {
        bool canMoveTo(int target) const {
            return target % 2 != 0;
        }
    }; DYNAMIX_DEFINE_MIXIN(AfraidOfEvens, priority(1, canMoveTo_msg));
    int main() {
        object o;
        mutate(o).add<FlyingCreature>();
        moveTo(o, 10); // flying to 10
        mutate(o).add<AfraidOfEvens>();
        moveTo(o, 10); // can't fly to 10 
    }
            

Eye candy time!

MixQuest: github.com/iboB/mixquest

2. Hotswap

Hotswap?


  • Change code in the program while it's running
  • Typically a development-only feature
  • Most business logic programmers are used to this

Stateless hotswap


  • Trivial in dynamic languages
  • But it's even easy in C++: shared libraries
  • Why don't you use it?

    using foo_func_type = int (*)(float, MyType*);
    foo_func_type foo_func;
    void on_foo_file_changed() {
        if (lib) dlclose(lib);
        lib = dlopen("foo.so", RTLD_NOW);
        foo_func = (foo_func_type)dlsym(lib, "foo_func");
    }
                

Eye candy time!

MixQuest again: github.com/iboB/mixquest

C++: Runtime Compiled C++

Hotpatching: Edit-and-continue, Live++

Stateful hotswap


        serialize_state();
        perform_hotswap();
        deserialize_state();
                
  • Easy in dynamic languages thanks to reflection
  • Hard on C++ with no reflection

Hotswapping C++ is real. Use it today!

3. REPL

Eye candy time!

Google chrome

Wait... We can have this in C++?

well... sorta

Interpreting C++


  • Ch and cling
  • Embedding... it's tough
  • You probably have to go all in
    • Unexpected bonuses
    • It may be too slow
    • It may be too slow to start
  • Ch may sound nice but it doesn't support C++

Hotswapping on steroids


  • The hacky approach
  • What if we create a shared library for each line of REPL code?



Eye candy time!

RCRL: https://github.com/onqtam/rcrl

There is more.

More


  1. Build-time optimizations
  2. Testing: gtest, DocTest, Catch2
  3. Mocking: gtest (gmock), FakeIt
  4. Property-based testing and fuzzing: libFuzzer, clang plugins and the future.

The beast is back! - Jon Kalb

End

Questions?


Borislav Stanimirov / ibob.github.io / @stanimirovb

Link to these slides: http://ibob.github.io/slides/bbb2cpp/
Slides license Creative Commons By 3.0
Creative Commons License