--- title: "Polymorphic Flyweight in C++" date: 2020-07-22T16:16:39+0200 draft: false # I don't care for draft mode, git has branches for that description: "A no-boilerplate flyweight pattern" tags: - design pattern - C++ categories: - programming series: - Generic flyweight favorite: false --- Coming back from our last post about [generic flyweights in C++]({{< relref "generic-flyweight-cpp.md" >}}), we can write a flyweight that can be used with any abstract base classes. ## Motivation I was writing a raytracer in C++, and used an abstract base class to represent textures. Having to potentially instantiate numerous identical textures, I wanted to avoid that problem and instead use a flyweight for my textures. I first thought about using the generic flyweight scheme that I presented last time, however to do so I need a way to store and compare my objects. I needed some infrastructure to extend the technique to be useful for my new use case. ## RTTI and order My textures' interface are the way I want to manipulate them in my raytracer, I cannot have access to the underlying type, and do not want to have to juggle their types outside of the implementation. I also want to use RAII effectively, to avoid the headache of juggling lifetimes. To this effect, I need to store some `std::unique_ptr` inside my set. However, to do so I need to come up with a way to totally order my values behind the `TextureInterface`. [std::type_info](https://en.cppreference.com/w/cpp/types/type_info) comes to the rescue: more specifically its sibling class [std::type_index](https://en.cppreference.com/w/cpp/types/type_info/before). I could order my textures by their `std::type_index` to order them first by chunks of types, and then sort the values inside the chunks by calling an ordering method on my polymorphic objects. ## Implementation The abstract class looked like this: ```cpp class AbstractTexture { // Abstract method to compare two instances of the same class virtual bool less_than(const AbstractTexture& other) = 0; public: virtual ~AbstractTexture() = default; // Abstract class => virtual destructor friend operator<(const AbstractTexture& lhs, const AbstractTexture& rhs) { const std::type_index lhs_i(lhs); const std::type_index rhs_i(rhs); if (lhs_i != rhs_i) returh lhs_i < rhs_i; // We are now assured that both classes have the same type return less_than(rhs); } }; ``` And one of its children should be implemented like this: ```cpp class UniformTexture : public AbstractTexture { Color color_; bool less_than(const AbstractTexture& other) override { // We are assured that 'other' is of the same type at this point const auto& rhs = dynamic_cast(other); return color_ < rhs.color_; // Return appropriate order } }; ``` We can now create a flyweight texture class by doing: ```cpp // Nice alias for a pointer to AbstractTexture using texture_ptr = std::unique_ptr; // An implementation of the 'less_than' comparison for 'texture_ptr' class TextureCmp { bool operator()(const texture_ptr& lhs, const texture_ptr& rhs) { return *lhs < *rhs; // Proxy to operator< for AbstractTexture } } class Texture : Unique { // Implement using the underlying AbstractTexture interface }; ``` ## Conclusion I have now showed you this technique to implement a flyweight for any abstract class you might want to use it for. Having neither seen anybody use an `std::set` to store flyweights, nor anybody writing tutorials about storing polymorphic objects in flyweights, I had a blast trying to figure out an elegant solution to this problem. My first implementation was complicated by the use of `std::type_info` directly, basically re-implementing `std::type_index` because I had missed it while reading the docs. We could specialise our `Unique` class to avoid the double dereferencing when storing pointers in our set (instead of having the Unique store a pointer to the stored smart-pointer). We could also automatise the use of a comparison functor similar to the `TextureCmp` class defined above when storing pointer-like values in the set. Both of those are left as an exercise to the reader :winking_face: