DynaMix
1.3.7
A new take on polymorphism in C++
|
DynaMix is a library which allows the composition and modification of polymorphic objects at run time. Objects are constructed from 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 resembles 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, DynaMix is an alternative way to accomplish polymorphism. It is a means to create a project's architecture, rather than implement its purpose.
The library uses the type dynamix::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.
DynaMix focuses on maximal performance and minimal memory overhead.
The term "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.
In D the term mixin exists and 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 the mixins in DynaMix. 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:
For a more detailed, working example see the basic usage tutorial.
std::function
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, DynaMix 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 DynaMix's features will almost certainly lead to cache misses. Of course, you may still rely on the library in other parts of your code, like the business (or gameplay) logic. 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 DynaMix.
The closest thing to DynaMix that C++ can offer out of the box is multiple inheritance. A dynamix::object
composed of some mixins, can be thought of as an instance of 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.
Here's a comprehensive list of the most important differences between DynaMix and C++ multiple inheritance:
dynamix::object
.However, even-though compared to other libraries that have similar features, DynaMix 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 to foo
or bar
. Since the library uses a placeholder type – dynamix::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 dynamix::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.dynamix::objects
naturally takes up the same amount plus an additional pointer for the type metadata, plus N pointers used for the special dm_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. More specifically the Interface to Component pattern
If you're not familiar with entity-component-systems, you might want to check out the appendix entry on them.
Here's how DynaMix is like an Interface to Component ECS:
dynamix::object
can be interpreted as an entity in an ECS. It's just an empty class, that needs to be "built" from mixins.However, DynaMix is not strictly an ECS. Here's a list of the differences.
dynamix::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, DynaMix 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. Still, a non-polymorphic ECS and DynaMix can be (and have been) used together in a signle piece of software where DynaMix is used in the business logic subsystem, while the performance critical low-level parts of the software make use of a non-polymorphic ECS.
There are indeed similarities between DynaMix and Microsoft Component Object Model. DynaMix object are composed out of mixins much like COM's objects are composed from components. Indeed many patterns in DynaMix might be familiar to programmers familiar with COM. The notable differences are:
Release
method.As mentioned above DynaMix 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 DynaMix 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:
DynaMix 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 DynaMix.
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 DynaMix 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 DynaMix allows you to do much more: