Last active
May 1, 2023 16:01
-
-
Save vladiant/3ad3ace6e010378213b127bca25b879d to your computer and use it in GitHub Desktop.
C+ Type erasure
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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