posts: add polymorphic flyweight post
This commit is contained in:
parent
d70db49486
commit
970a30167a
128
content/posts/polymorphic-flyweight-cpp.md
Normal file
128
content/posts/polymorphic-flyweight-cpp.md
Normal file
|
@ -0,0 +1,128 @@
|
|||
---
|
||||
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.
|
||||
|
||||
<!--more-->
|
||||
|
||||
## 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<TextureInterface>` 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<const UniformTexture&>(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<AbstractTexture>;
|
||||
|
||||
// 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<texture_ptr, TextureCmp> {
|
||||
// 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:
|
Loading…
Reference in a new issue