Boost.Mixin is a library that allows the composition and modifications of polymorphic types at run time. Types and objects are constructed out of building blocks called mixins enabling an effect similar to multiple inheritance, while allowing the client code to remain oblivious to the actual composition of the objects.
A take on the Composition over inheritance technique, the result closely resembles the popular pattern entity-component-system, or the mixins in Ruby. It can also be compared to the inheritance in Eiffel or the traits in Self, Scala, PHP, and many others, or the roles in Perl.
This is given while also having full abstraction between the interface and the definition of types – a problem often given as the motivation for the PIMPL idiom.
In short, Boost.Mixin is an alternative way to accomplish polymorphism.
The library uses the type boost::mixin::object
as a placeholder, whose instances can be extended with existing classes (mixins),
thus providing a particular instance with the functionality of all those
types. Accessing the newly formed type's interface is made through messages
– stand-alone functions generated by the library, which can be thought
of as methods.
Boost.Mixin focuses on maximal performance and minimal memory overhead.
The name "Mixin" is not to be confused with another meaning, popular in C++, namely CRTP mixins. This particular use of CRTP in this document shall henceforth be referred to as "traits", as the exact same functionality is called "traits" in many other languages.
mixin
is
a keyword. It is a compile time feature, that has some similarities
with the macros in C and C++, and none at all with Boost.Mixin. It
is a completely different concept.
Here is a very small and incomplete example of what your code may look like if you use the library.
// assuming my_objects.get_ally(0); is a way to get an ally to the // main character in a game boost::mixin::object& obj = my_objects.get_ally(0); // now let's make the object think some positive thoughts about the // main character think(obj); // C++ doesn't allow us to have obj.think(). // Boost.Mixin's messages are standalone functions // type composition boost::mixin::mutate(obj) .add<flying_creature>(); // object can now respond to fly() fly(obj); // ...instead of obj.fly() // type mutation boost::mixin::mutate(obj) .remove<ally>() .add<enemy>(); think(obj); // the same object now thinks negative thoughts about the main // character, since it's no longer an ally, but an enemy
For a more detailed, working example see Basic usage.
The more complex your objects are, the more beneficial it will be to use the library. Pieces of software that typically have very complex objects include Games (especially role-playing ones or strategies), CAD systems, enterprise systems, UI libraries, and more.
As a general rule of thumb: if you have complex polymorphic objects, Boost.Mixin is a good choice.
We should emphasize on the polymorphism. In many very high-performance systems
polymorphism is avoided, at the cost of code that is (at least somewhat)
harder to write and maintain (this is most often the case with high-end games).
Since such systems will try to "squeeze" every possible piece of
processing power out of the CPU, cache locality and lack of cache misses
are critical in some parts of their code. As is the case with all instances
of polymorphism, including C++ virtual methods and std::function
,
uses of Boost.Mixin's features will almost certainly lead to cache misses.
Of course, you may still rely on the library in other parts of your code.
For more information about the library performance, see the Performance
section.
Of course, small projects with simple objects, even if they are polymorphic, may end up not finding any particular benefits in using the library, since their size makes them fast to compile and easy to maintain as they are. If a piece of software can be created in a couple of days, by one or two programmers, there will hardly be any need for Boost.Mixin.
The closest thing to Boost.Mixin that C++ can offer out of the box is multiple
inheritance. A boost::mixin::object
composed of some mixins, can be
thought of as an empty class that's derived from these classes –
the object's interface will be equal to the union of the interfaces of
its mixins, and it will internally instantiate them.
We covered how much more than multiple inheritance the library is in the previous section, but in case you haven't inferred everything, here a comprehensive list of the most important differences between Boost.Mixin and C++ multiple inheritance:
boost::mixin::object
.
However, even-though compared to other libraries that have similar features, Boost.Mixin is one of the fastest and with the least memory overhead, using the library comes with some inevitable downsides when compared with plain multiple inheritance:
foo
and
bar
can implicitly
be cast too foo
or
bar
. Since the library
uses a placeholder type – boost::mixin::object
– implicit casts to any of its mixins are impossible. That aside,
the hypothetical object from above, will receive all of the methods
from foo and bar, but while this is true for the mixin::object
with such mixins, a compilation error cannot be generated if a message
is called that the object can't handle. A runtime error will be generated
instead, which is the norm in similar libraries, but is harder to catch
and debug than it is with plain multiple inheritance.
boost::mixin::objects
naturally takes up the same
amount plus an additional pointer for the type metadata, plus N pointers
used for the special bm_this
pointer, where N is equal to the number of mixins within. So, in short,
the memory overhead of an object composed on N mixins, is N+1 times
sizeof(intptr_t)
((N+1) * 8 bytes on a 64 bit system).
std::function
call in terms of speed,
which in most (but not all) cases is a negligible overhead.
If you're familiar with entity-component-systems, one way of looking at at the library is as if it is one of those, and indeed, it has many features that are characteristic for such systems.
If you're not familiar with entity-component-systems, you might want to check out the appendix entry on them.
Here's how Boost.Mixin is like an ECS:
boost::mixin::object
can be interpreted as an entity
in an ECS. It's just an empty class, that needs to be "built"
from mixins.
However, Boost.Mixin is not strictly an ECS. Here's a list of the differences.
boost::mixin::object
-s
can be (and are) completely oblivious to what kinds of mixins there
may be, allowing you to truly, physically separate a program's subsystems.
The entity in an ECS on the other hand, usually has at least some knowledge
of all the possible component types (like the top level parent classes,
for example).
Note that some data structures, sometimes called entity-component-systems, are not used to create and manage polymorphic objects, but instead are used to bind a strictly predefined set of concrete components to the same entity for a very performance intensive piece of software. The components are used by different subsystems, that require them to be aligned in dense arrays for faster processing, without cache misses. As we mentioned before, Boost.Mixin isn't designed for such cases, and while in terms of design and ease of use, it is a better choice than such an approach, it cannot help you in their main goal.
As mentioned above Boost.Mixin is similar to a feature of many other languages, called "traits". The exact same feature can be mimicked in C++ with CRTP. You may have heard the term "mixin" being mentioned in a C++ context before. It is very likely that what was meant was this CRTP style of creating types from existing ones.
Indeed, at least superficially, both Boost.Mixin and said traits are very similar as both are used to create new types by combining existing ones, while also solving one of the problems of plain old multiple inheritance - the communication between the different components that comprise the object.
However much of the multiple inheritance problems remain:
Boost.Mixin solves all of these problems.
If you're familiar with the mixins in Ruby, perhaps you will find a lot of similarities between them and Boost.Mixin.
Ruby's mixins can of course be used just like traits, however extending
existing objects with modules is also allowed via Object#extend
. This is almost exactly what
Boost.Mixin allows you to do. Indeed, Ruby has been a great inspiration
for this library.
Still, barring the differences that arise from C++ being a strongly typed language, for a small amount of extra code Boost.Mixin allows you to do much more: