Skip to content

Instantly share code, notes, and snippets.

@mrkline
Last active May 13, 2022 08:08
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mrkline/114fa9963a6615231c40247e129c21b4 to your computer and use it in GitHub Desktop.
Save mrkline/114fa9963a6615231c40247e129c21b4 to your computer and use it in GitHub Desktop.
Sean Parent's polymorphism demo
// See http://sean-parent.stlab.cc/presentations/2016-10-10-runtime-polymorphism/2016-10-10-runtime-polymorphism.pdf
// or an earlier version, "Inheritance Is The Base Class of Evil"
#include <iostream>
#include <memory>
#include <string>
#include <vector>
using namespace std;
// Print anything iostreams knows how to:
template <typename T>
void draw(const T& x, ostream& out, size_t indent)
{
out << string(indent, ' ') << x << endl;
}
// An object holds any type T which has a corresponding function
// `void draw(const T&, ostream& out, size_t indent)`.
// It uses polymorphism to dispatch the correct draw function for each instance
// at runtime, but that polymorphism is an implementation detail,
// **not** a mandatory part of the client code/interface.
class Object {
public:
template <typename T>
Object(T x) : self(make_shared<Model<T>>(move(x))) { }
// Make this a freestanding friend function (instead of a member)
// so that it shares the _exact_ same interface as other draw functions.
// We can now embed Objects in Objects.
friend void draw(const Object& x, ostream& out, size_t indent)
{
x.self->_draw(out, indent);
}
private:
// Here we define our interface.
struct Drawable {
virtual ~Drawable() = default; // virtual dtor needed for all interfaces
virtual void _draw(ostream&, size_t) const = 0;
};
// Here we define the model for a particular type T.
template <typename T>
struct Model final : Drawable {
Model(T x) : data(move(x)) { }
void _draw(ostream& out, size_t indent) const override
{
// This calls the correct draw() function for whatever type it happens to hold
draw(data, out, indent);
}
T data;
};
// Making this a shared_ptr to const data has a few really nice properties:
// 1. We get automatic copy-on-write (CoW) semantics.
// Making a copy of ourselves just bumps the reference count.
//
// 2. The default special functions (move, assignment, etc.) work great.
// If this was a unique_ptr instead, we'd have to implement our own
// copy operators for Object, and have a virtual copy() function for
// Drawable.
shared_ptr<const Drawable> self;
};
// Let's make a "document" a vector of Objects, and say how to print it.
using Document = vector<Object>;
// Note how composable this design is. We can use the exact same interface
// for things like std::vector. Had this design demanded inheritance,
// we would have to make our own wrapper classes around vector, etc.
// (Mantis does this unfortunately often.)
void draw(const Document& x, ostream& out, size_t indent)
{
const auto indentString = string(indent, ' ');
out << indentString << "{" << endl;
for (const auto& e : x) draw(e, out, indent + 4);
out << indentString << "}" << endl;
}
// We can insert arbitrary things too, so long as they have a draw()
class Foo {
public:
Foo() = default;
Foo(int x_) : x(x_) { }
int x = 42;
};
void draw(const Foo& f, ostream& out, size_t indent)
{
out << string(indent, ' ') << "I am a foo with value " << f.x << endl;
}
// Actual use:
int main()
{
Document d;
d.push_back(42);
d.push_back("O hi!");
d.push_back(Foo(29));
draw(d, cout, 0);
cout << "-------------" << endl;
d.push_back(d); // CoW!
d.push_back("Ta da!");
draw(d, cout, 0);
return 0;
}
@mrkline
Copy link
Author

mrkline commented Jan 20, 2017

Sample output:

{
    42
    O hi!
    I am a foo with value 29
}
-------------
{
    42
    O hi!
    I am a foo with value 29
    {
        42
        O hi!
        I am a foo with value 29
    }
    Ta da!
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment