Skip to content

Instantly share code, notes, and snippets.

@vladiant
Last active May 1, 2023 16:01
Show Gist options
  • Save vladiant/3ad3ace6e010378213b127bca25b879d to your computer and use it in GitHub Desktop.
Save vladiant/3ad3ace6e010378213b127bca25b879d to your computer and use it in GitHub Desktop.
C+ Type erasure
#include <iostream>
#include <memory>
#include <vector>
// Breaking Dependencies - C++ Type Erasure - The Implementation Details - Klaus Iglberger CppCon 2022
// https://www.youtube.com/watch?v=qn6OqefuH08
struct Circle {
public:
explicit Circle(double rad) : radius{rad} {}
double getRadius() const noexcept { return radius; }
private:
double radius;
};
struct Square {
explicit Square(double s) : side{s} {}
double getSide() const noexcept { return side; }
private:
double side;
};
class Shape {
struct ShapeConcept {
virtual ~ShapeConcept() = default;
virtual void do_serialize() const = 0;
virtual void do_draw() const = 0;
virtual std::unique_ptr<ShapeConcept> clone() const = 0;
};
template <typename ShapeT>
struct ShapeModel : public ShapeConcept {
ShapeModel(ShapeT shape) : shape_{std::move(shape)} {}
std::unique_ptr<ShapeConcept> clone() const override {
return std::make_unique<ShapeModel>(*this);
}
void do_serialize() const override { serialize(shape_); }
void do_draw() const override { draw(shape_); }
ShapeT shape_;
};
friend void serialize(const Shape& shape) { shape.pimpl->do_serialize(); }
friend void draw(const Shape& shape) { shape.pimpl->do_draw(); }
std::unique_ptr<ShapeConcept> pimpl;
public:
template <typename ShapeT>
Shape(ShapeT shape)
: pimpl{std::make_unique<ShapeModel<ShapeT>>(std::move(shape))} {}
// Copy operations
Shape(const Shape& other) : pimpl{other.pimpl->clone()} {}
Shape& operator=(const Shape& other) {
other.pimpl->clone().swap(pimpl);
return *this;
}
// Move operations
// Shape(Shape&& other);
Shape& operator=(Shape&& other) noexcept {
pimpl.swap(other.pimpl);
return *this;
}
};
void serialize(const Circle& circle) {
std::cout << "Serialize Circle: " << circle.getRadius() << '\n';
}
void draw(const Circle& circle) {
std::cout << "Draw Circle: " << circle.getRadius() << '\n';
}
void serialize(const Square& square) {
std::cout << "Serialize Square: " << square.getSide() << '\n';
}
void draw(const Square& square) {
std::cout << "Draw Square: " << square.getSide() << '\n';
}
void drawAllShapes(const std::vector<Shape>& shapes) {
for (const auto& shape : shapes) {
draw(shape);
}
}
int main() {
using Shapes = std::vector<Shape>;
Shapes shapes;
shapes.emplace_back(Circle(2.0));
shapes.emplace_back(Square(1.5));
shapes.emplace_back(Circle(4.2));
drawAllShapes(shapes);
return 0;
}
#include <array>
#include <iostream>
#include <memory>
#include <vector>
// Breaking Dependencies - C++ Type Erasure - The Implementation Details - Klaus Iglberger CppCon 2022
// https://www.youtube.com/watch?v=qn6OqefuH08
struct Circle {
public:
explicit Circle(double rad) : radius{rad} {}
double getRadius() const noexcept { return radius; }
private:
double radius;
};
struct Square {
explicit Square(double s) : side{s} {}
double getSide() const noexcept { return side; }
private:
double side;
};
class Shape {
struct ShapeConcept {
virtual ~ShapeConcept() = default;
virtual void do_serialize() const = 0;
virtual void do_draw() const = 0;
virtual std::unique_ptr<ShapeConcept> clone() const = 0;
};
template <typename ShapeT>
struct ShapeModel : public ShapeConcept {
ShapeModel(ShapeT shape) : shape_{std::move(shape)} {}
std::unique_ptr<ShapeConcept> clone() const override {
return std::make_unique<ShapeModel>(*this);
}
void do_serialize() const override { serialize(shape_); }
void do_draw() const override { draw(shape_); }
ShapeT shape_;
};
friend void serialize(const Shape& shape) { shape.pimpl->do_serialize(); }
friend void draw(const Shape& shape) { shape.pimpl->do_draw(); }
std::unique_ptr<ShapeConcept> pimpl;
public:
template <typename ShapeT>
Shape(ShapeT shape)
: pimpl{std::make_unique<ShapeModel<ShapeT>>(std::move(shape))} {}
// Copy operations
Shape(const Shape& other) : pimpl{other.pimpl->clone()} {}
Shape& operator=(const Shape& other) {
other.pimpl->clone().swap(pimpl);
return *this;
}
// Move operations
// Shape(Shape&& other);
Shape& operator=(Shape&& other) noexcept {
pimpl.swap(other.pimpl);
return *this;
}
};
class ShapeConstRef {
public:
template <typename ShapeT>
ShapeConstRef(const ShapeT& shape)
: shape_{std::addressof(shape)}, draw_{[](const void* shape) {
draw(*static_cast<const ShapeT*>(shape));
}} {}
friend void draw(const ShapeConstRef& shape) { shape.draw_(shape.shape_); }
private:
using DrawOperation = void(const void*);
void const* shape_{nullptr};
DrawOperation* draw_{nullptr};
};
void serialize(const Circle& circle) {
std::cout << "Serialize Circle: " << circle.getRadius() << '\n';
}
void draw(const Circle& circle) {
std::cout << "Draw Circle: " << circle.getRadius() << '\n';
}
void serialize(const Square& square) {
std::cout << "Serialize Square: " << square.getSide() << '\n';
}
void draw(const Square& square) {
std::cout << "Draw Square: " << square.getSide() << '\n';
}
void drawAllShapes(const std::vector<Shape>& shapes) {
for (const auto& shape : shapes) {
draw(shape);
}
}
int main() {
using Shapes = std::vector<Shape>;
Shapes shapes;
shapes.emplace_back(Circle(2.0));
shapes.emplace_back(Square(1.5));
shapes.emplace_back(Circle(4.2));
drawAllShapes(shapes);
return 0;
}
#include <array>
#include <iostream>
#include <memory>
#include <vector>
// Breaking Dependencies - C++ Type Erasure - The Implementation Details - Klaus Iglberger CppCon 2022
// https://www.youtube.com/watch?v=qn6OqefuH08
struct Circle {
public:
explicit Circle(double rad) : radius{rad} {}
double getRadius() const noexcept { return radius; }
private:
double radius;
};
struct Square {
explicit Square(double s) : side{s} {}
double getSide() const noexcept { return side; }
private:
double side;
};
// Storage Policy
template <size_t buffersize = 1280UL, size_t alignment = 16UL>
class Shape {
struct ShapeConcept {
virtual ~ShapeConcept() = default;
virtual void do_serialize() const = 0;
virtual void do_draw() const = 0;
virtual void clone(ShapeConcept* memory) const = 0;
virtual void move(ShapeConcept* memory) const = 0;
};
template <typename ShapeT>
struct ShapeModel : public ShapeConcept {
ShapeModel(ShapeT shape) : shape_{std::move(shape)} {}
void clone(ShapeConcept* memory) const override {
::new (memory) ShapeModel(*this);
}
void move(ShapeConcept* memory) const override {
::new (memory) ShapeModel(std::move(*this));
}
void do_serialize() const override { serialize(shape_); }
void do_draw() const override { draw(shape_); }
ShapeT shape_;
};
template <typename ShapeT, typename DrawStrategy>
struct ExtendedModel : public ShapeConcept {
explicit ExtendedModel(ShapeT shape, DrawStrategy drawer)
: shape_{std::move(shape)}, drawer_{std::move(drawer)} {}
void do_draw() const override { drawer_(shape_); }
void do_serialize() const override { serialize(shape_); }
std::unique_ptr<ShapeConcept> clone() const override {
return std::make_unique<ExtendedModel>(*this);
}
ShapeT shape_;
DrawStrategy drawer_;
};
friend void serialize(const Shape& shape) { shape.pimpl()->do_serialize(); }
friend void draw(const Shape& shape) { shape.pimpl()->do_draw(); }
ShapeConcept* pimpl() noexcept {
return reinterpret_cast<ShapeConcept*>(buffer.data());
}
const ShapeConcept* pimpl() const noexcept {
return reinterpret_cast<const ShapeConcept*>(buffer.data());
}
alignas(alignment) std::array<std::byte, buffersize> buffer;
public:
template <typename ShapeT>
Shape(ShapeT shape) {
using M = ShapeModel<ShapeT>;
static_assert(sizeof(M) <= buffersize, "Given type is too large");
static_assert(alignof(M) <= alignment, "Given type is overaligned");
::new (pimpl()) M(shape);
}
// Special member functions
~Shape() { pimpl()->~ShapeConcept(); }
// Copy operations
Shape(const Shape& other) { other.pimpl()->clone(pimpl()); }
Shape& operator=(const Shape& other) {
Shape copy{other};
buffer.swap(copy.buffer);
return *this;
}
// Move operations
Shape(Shape&& other) { other.pimpl()->move(pimpl()); }
Shape& operator=(Shape&& other) noexcept {
Shape tmp{std::move(other)};
buffer.swap(tmp.buffer);
return *this;
}
};
void serialize(const Circle& circle) {
std::cout << "Serialize Circle: " << circle.getRadius() << '\n';
}
void draw(const Circle& circle) {
std::cout << "Draw Circle: " << circle.getRadius() << '\n';
}
void serialize(const Square& square) {
std::cout << "Serialize Square: " << square.getSide() << '\n';
}
void draw(const Square& square) {
std::cout << "Draw Square: " << square.getSide() << '\n';
}
void drawAllShapes(const std::vector<Shape<>>& shapes) {
for (const auto& shape : shapes) {
draw(shape);
}
}
int main() {
using Shapes = std::vector<Shape<>>;
Shapes shapes;
shapes.emplace_back(Circle(2.0));
shapes.emplace_back(Square(1.5));
shapes.emplace_back(Circle(4.2));
drawAllShapes(shapes);
return 0;
}
#include <iostream>
#include <memory>
#include <vector>
// Breaking Dependencies - C++ Type Erasure - The Implementation Details - Klaus Iglberger CppCon 2022
// https://www.youtube.com/watch?v=qn6OqefuH08
struct Circle {
public:
explicit Circle(double rad) : radius{rad} {}
double getRadius() const noexcept { return radius; }
private:
double radius;
};
struct Square {
explicit Square(double s) : side{s} {}
double getSide() const noexcept { return side; }
private:
double side;
};
class Shape {
struct ShapeConcept {
virtual ~ShapeConcept() = default;
virtual void do_serialize() const = 0;
virtual void do_draw() const = 0;
virtual std::unique_ptr<ShapeConcept> clone() const = 0;
};
template <typename ShapeT>
struct ShapeModel : public ShapeConcept {
ShapeModel(ShapeT shape) : shape_{std::move(shape)} {}
std::unique_ptr<ShapeConcept> clone() const override {
return std::make_unique<ShapeModel>(*this);
}
void do_serialize() const override { serialize(shape_); }
void do_draw() const override { draw(shape_); }
ShapeT shape_;
};
template <typename ShapeT, typename DrawStrategy>
struct ExtendedModel : public ShapeConcept {
explicit ExtendedModel(ShapeT shape, DrawStrategy drawer)
: shape_{std::move(shape)}, drawer_{std::move(drawer)} {}
void do_draw() const override { drawer_(shape_); }
void do_serialize() const override { serialize(shape_); }
std::unique_ptr<ShapeConcept> clone() const override {
return std::make_unique<ExtendedModel>(*this);
}
ShapeT shape_;
DrawStrategy drawer_;
};
friend void serialize(const Shape& shape) { shape.pimpl->do_serialize(); }
friend void draw(const Shape& shape) { shape.pimpl->do_draw(); }
std::unique_ptr<ShapeConcept> pimpl;
public:
template <typename ShapeT>
Shape(ShapeT shape)
: pimpl{std::make_unique<ShapeModel<ShapeT>>(std::move(shape))} {}
template <typename ShapeT, typename DrawStrategy>
Shape(ShapeT shape, DrawStrategy drawer)
: pimpl{std::make_unique<ExtendedModel<ShapeT, DrawStrategy>>(
std::move(shape), std::move(drawer))} {}
// Copy operations
Shape(const Shape& other) : pimpl{other.pimpl->clone()} {}
Shape& operator=(const Shape& other) {
other.pimpl->clone().swap(pimpl);
return *this;
}
// Move operations
// Shape(Shape&& other);
Shape& operator=(Shape&& other) noexcept {
pimpl.swap(other.pimpl);
return *this;
}
};
void serialize(const Circle& circle) {
std::cout << "Serialize Circle: " << circle.getRadius() << '\n';
}
void draw(const Circle& circle) {
std::cout << "Draw Circle: " << circle.getRadius() << '\n';
}
void serialize(const Square& square) {
std::cout << "Serialize Square: " << square.getSide() << '\n';
}
void draw(const Square& square) {
std::cout << "Draw Square: " << square.getSide() << '\n';
}
void drawAllShapes(const std::vector<Shape>& shapes) {
for (const auto& shape : shapes) {
draw(shape);
}
}
int main() {
using Shapes = std::vector<Shape>;
Shapes shapes;
shapes.emplace_back(Circle(2.0));
shapes.emplace_back(Square(1.5));
shapes.emplace_back(Circle(4.2), [](const Circle& circle) {
std::cout << "Strategy Draw Circle: " << circle.getRadius() << '\n';
});
drawAllShapes(shapes);
return 0;
}
#include <iostream>
#include <variant>
class IBase {
public:
virtual void act() = 0;
virtual void set(int a) = 0;
virtual int get() = 0;
};
class FirstImpl : public IBase {
public:
FirstImpl() = default;
FirstImpl(int arg) : m_{arg} {}
void act() override { std::cout << "FirstImpl act\n"; }
void set(int a) override {
std::cout << "FirstImpl set " << a << "\n";
m_ = a;
}
int get() override {
std::cout << "FirstImpl get\n";
return m_;
}
private:
int m_{11};
};
class SecondImpl : public IBase {
public:
void act() override { std::cout << "SecondImpl act\n"; }
void set(int a) override {
std::cout << "SecondImpl set " << a << "\n";
m_ = a;
}
int get() override {
std::cout << "SecondImpl get\n";
return m_;
}
private:
int m_{13};
};
class Base : public IBase {
void act() override {
std::cout << "Base act\n";
std::visit([this](auto& arg) { arg.act(); }, object);
}
void set(int a) override {
std::cout << "Base set " << a << "\n";
std::visit([this, a](auto& arg) { arg.set(a); }, object);
}
int get() override {
std::cout << "Base get\n";
return std::visit([this](auto& arg) { return arg.get(); }, object);
}
using var_t = std::variant<FirstImpl, SecondImpl>;
var_t object;
static Base inst;
public:
static Base& instance() { return inst; }
static void setFirstImpl(FirstImpl&& a = FirstImpl()) {
inst.object = std::move(a);
}
static void setSecondImpl() { inst.object = SecondImpl(); }
};
Base Base::inst;
int main() {
IBase& test = Base::instance();
std::cout << "*** Unset ***\n";
test.act();
std::cout << test.get() << '\n';
test.set(19);
std::cout << test.get() << '\n';
Base::instance().setSecondImpl();
std::cout << "*** SecondImpl ***\n";
test.act();
std::cout << test.get() << '\n';
test.set(23);
std::cout << test.get() << '\n';
Base::instance().setFirstImpl(FirstImpl(137));
std::cout << "*** FirstImpl ***\n";
test.act();
std::cout << test.get() << '\n';
test.set(29);
std::cout << test.get() << '\n';
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment