(For the complete, working source of this example see messages.cpp)
For this tutorial we'll look at a simplified piece of code from an imaginary game. First let's define the mixin classes that we're going to use.
There's a mixin that's a part from every object of our game. The one that
gives them a unique id. We'll also define a method, called trace
that will display information about
the mixin in a stream.
class has_id { public: void set_id(int id) { _id = id; } int id() const { return _id; } void trace(ostream& out) const; private: int _id; };
Next we'll define a class for an animated model. We could have other types of models in the game, but for this tutorial there's no need to define anything more.
The mixin offers us a way to set a mesh and two ways to set an animation. It has a render method and, again the trace method, to display info about this mixin.
class animated_model { public: void set_mesh(const string& mesh); void set_animation(const string& animation); void set_animation(int anim_id); void render() const; void trace(ostream& out) const; private: string _mesh; string _animation; };
Now we'll define three types of mixins that will give us artificial intelligence
logic for different occasions. They all share a method called think
for the AI, and the familiar trace
method.
class enemy_ai { public: void think(); void trace(ostream& out) const; }; class ally_ai { public: void think(); void trace(ostream& out) const; }; class stunned_ai { public: void think(); void trace(ostream& out) const; };
Now it's time to declare the messages our mixins will use. We have some methods
in our classes for which there won't be any messages, since those methods
aren't polymorphic. They're unique for their specific classes so it's absolutely
adequate to call them by object.get<mixin>()->method(...)
.
So, let's start with the simplest case. The one we already used in the basic usage example.
The declaration syntax is the familiar macro BOOST_MIXIN_MESSAGE_|N|
,
where |N|
stands for the number of arguments the message
has. The macro's arguments are coma separated: return value, message/method
name, argument 1 type, argument 1 name, argument 2 type, argument 2 name,
etc etc.
This simple case is covered by the messages think
and set_mesh
[1]
BOOST_MIXIN_MESSAGE_0(void, think); BOOST_MIXIN_MESSAGE_1(void, set_mesh, const string&, mesh);
Now it may seem that render
is also a pretty simple example of a message, but there's a small difference.
It's supposed to be handled by const methods. This makes it a const message
and as such it has a different declaration macro – the same as before
but with CONST
added to it:
BOOST_MIXIN_CONST_MESSAGE_0(void, render);
Lets see the trace
method,
that's present in all of our classes. If we declare a message for it in the
way we talked above, only of the mixins within an object will be able to
handle it. But when we trace
an object's info, we obviously would like to have the info for all of its
mixins. For cases like this: where more than one of the mixins in an object
is supposed to handle a message, Boost.Mixin introduces multicast
messages. You declare those by adding MULTICAST
to the macro (before MESSAGE
but after CONST
if it's a
const one)
BOOST_MIXIN_CONST_MULTICAST_MESSAGE_1(void, trace, ostream&, out);
The last type of message there is meant for overloaded methods. For these we need message overloads.
A message overload will require you to think of a special name, that's used to refer to that message, different from the name of the method. Don't worry. The stand-alone function that's generated for the message call itself will have the appropriate name (the method's name).
The macro used for message overloads is the same as before with OVERLOAD
at the end. The other difference
is that its first argument should be the custom name for the message (followed
by the type, method name, and method/message arguments like before).
In our case set_animation
has two overloads:
BOOST_MIXIN_MESSAGE_1_OVERLOAD(set_anim_by_name, void, set_animation, const string&, animation); BOOST_MIXIN_MESSAGE_1_OVERLOAD(set_anim_by_id, void, set_animation, int, anim_id);
As you might have guessed, any message could be defined as a message overload
and indeed in the case where there are no overloads BOOST_MIXIN_MESSAGE_N(ret,
message_name,
...)
will just expand to BOOST_MIXIN_MESSAGE_N_OVERLOAD(message_name,
ret,
message_name,
...)
So, now that we've declared all our messages it's time to define them.
The macro used for defining a message is always the same, regardless of the message's constness, mechanism (multicast/unicast), or overload. It has a single argument – the message's name.
BOOST_MIXIN_DEFINE_MESSAGE(think); BOOST_MIXIN_DEFINE_MESSAGE(set_mesh); BOOST_MIXIN_DEFINE_MESSAGE(render); BOOST_MIXIN_DEFINE_MESSAGE(trace);
For the overloads we should use our custom name:
BOOST_MIXIN_DEFINE_MESSAGE(set_anim_by_name); BOOST_MIXIN_DEFINE_MESSAGE(set_anim_by_id);
Great! Now that we have our messages it's time to define the classes from above as mixins.
Normally if our program is spread across several files, you should use BOOST_DECLARE_MIXIN
to declare that those
classes are mixins, but since our program is in a single file, it can be
omitted. All of its functionality is also in BOOST_DEFINE_MIXIN
.
We met the BOOST_DEFINE_MIXIN
macro from the basic example. It has two arguments – the mixin/class
name and its feature list. The feature list is a ampersand separated list
of symbols that represent the mixin's features. It can contain many things,
but for now we'll focus on messages – the ones this mixin is supposed
to handle.
The special thing here is that in order to distinguish the stand-alone function
that's generated to make message calls from the message, the library defines
a special symbol for each message. This symbol is used in the mixin feature
list and when checking whether a mixin implements a message. The symbol is
the message name postfixed with _msg
.
Let's define three of our simple mixins along with their feature (message) lists:
BOOST_DEFINE_MIXIN(enemy_ai, think_msg & trace_msg); BOOST_DEFINE_MIXIN(ally_ai, think_msg & trace_msg); BOOST_DEFINE_MIXIN(animated_model, trace_msg & set_mesh_msg & set_anim_by_id_msg & set_anim_by_name_msg & render_msg);
The reason we left out has_id
and stunned_ai
is because
we'd like to do something special with their message lists.
First, about has_id
. What
we'd like to do is display its info first, because the object id is usually
the first thing you need about an object. So in order to achieve this, the
notion of message priority is introduced. Each message in a mixin gets a
priority of 0 by default. For multicast messages, like trace
,
the priority will affect the order in which they're executed. The higher
priority a multicast message has in a mixin, the earlier it will be executed.
So if we set the priority of trace
in has_id
to something greater
than zero, we'll have a guarantee that when the object info is displayed,
its id will come first.
BOOST_DEFINE_MIXIN(has_id, priority(1, trace_msg));
For unicast messages the priority determines which of the potentially many mixin candidates will handle the message. Again, mixins with higher priority for a message are considered better candidates.
So if we set the priority of think
in stunned_ai
to something
greater than zero, then adding this mixin to an object that already has a
think message (like objects with enemy_ai
or ally_ai
), will hide it
previous implementation and override it with the one from stunned_ai
.
If we remove the mixin, the previous implementation will be exposed and will
resume handling the think
calls.
Also we'll consider stunned_ai
as a relatively uninteresting mixin, and set the priority of trace
to -1, and make its info be displayed
last (if at all available)
BOOST_DEFINE_MIXIN(stunned_ai, priority(1, think_msg) & priority(-1, trace_msg));
We're now ready to start using our mixins and messages in the simplified game.
Let's start by creating two objects - an enemy and an ally to the hypothetical main character. We'll give them some irrelevant id-s and meshes.
boost::mixin::object enemy; // just an empty boost::mixin::object boost::mixin::mutate(enemy) .add<has_id>() .add<animated_model>() .add<enemy_ai>(); enemy.get<has_id>()->set_id(1); set_mesh(enemy, "spider.mesh"); trace(enemy, cout); // trace enemy data boost::mixin::object ally; boost::mixin::mutate(ally) .add<has_id>() .add<animated_model>() .add<ally_ai>(); ally.get<has_id>()->set_id(5); set_mesh(ally, "dog.mesh"); trace(ally, cout); // trace ally data
Both calls to trace
from
above will display info about the newly constructed objects in the console.
think(enemy); // doing enemy stuff think(ally); // doing friendly stuff render(enemy); // drawing a hostile enemy render(ally); // drawing a friendly ally
Now lets try stunning our enemy. We'll just add the stunned_ai
mixin and, because of its special think
priority, the calls to think
from then on will be handled by it.
boost::mixin::mutate(enemy).add<stunned_ai>(); think(enemy); // don't do hostile stuff, because you're stunned render(enemy); // drawing a stunned enemy
Now let's remove the stun effect from our enemy, by simply removing the
stunned_ai
mixin from the
object. The handling of think
by enemy_ai
will resume as
before.
boost::mixin::mutate(enemy).remove<stunned_ai>(); think(enemy); // again do hostile stuff render(enemy); // drawing a hostile enemy
And that concludes our tutorial on messages.
(For the complete, working source of this example see mutation.cpp)
For this tutorial let's begin by introducing some mixins that may be found in a game: A root mixin, that should be present in all objects, and two that provide a way to render the object on the screen:
class game_object { int _id; string name; // ... other common fields }; class opengl_rendering {}; class directx_rendering {};
We won't concert ourselves with their concrete functionality, so we'll just leave them with no messages.
BOOST_DEFINE_MIXIN(game_object, boost::mixin::none); BOOST_DEFINE_MIXIN(opengl_rendering, boost::mixin::none); BOOST_DEFINE_MIXIN(directx_rendering, boost::mixin::none);
You're probably familiar from the previous examples with the most basic way to mutate an object, so let's use it to give this one a type.
boost::mixin::object obj1; boost::mixin::mutate(obj1) .add<game_object>() .add<opengl_rendering>();
...and then change it. Let's assume we're switching our rendering platform.
boost::mixin::mutate(obj1) .remove<opengl_rendering>() .add<directx_rendering>();
Using the mutate
class is
probably the most common way in which you'll mutate objects in Boost.Mixin.
Yes, mutate
is not a function
but a class. It has methods remove
and add
, and in its destructor
it applies the actual mutation.
A mutation is a relatively slow process so if the internal object type was
being changed on each call of remove
or add
, first the program
would be needlessly slowed down, and second the library would need to deal
with various incomplete types in its internal type registry.
So, if you want to add and remove mixins across several blocks or functions,
you may safely instantiate the mutate
class or use its typedef single_object_mutator
that probably has a more appropriate name for cases like this.
boost::mixin::single_object_mutator mutation(obj1); mutation.remove<directx_rendering>(); // ... mutation.add<opengl_rendering>();
Here obj1
hasn't been mutated
yet. A type that has game_object
and opengl_rendering
hasn't
been instantiated internally. In order for this to happen the mutation
instance needs to be destroyed,
or, to explicitly perform the mutation, you may call apply
like so:
mutation.apply();
Now obj1
has been mutated,
and mutation
has been "cleared"
– returned to the empty state it had right after its instantiation.
This means we can reuse it to perform other mutation on obj1
.
Say:
mutation.remove<game_object>();
Oops! We're removing the mixin that needs to be present in all objects. Not
to worry. You may "clear" a mutation without applying it, by calling
cancel
.
mutation.cancel();
Now the mutation is not performed, and its state is empty.
You may safely apply empty mutations to an object:
mutation.apply(); // no effect
Another way to mutate objects is by using a type template.
A type template gives a type to an object and, unlike mutate/single_object_mutator
it's not bound to a specific object instance. Again unlike mutate
it disregards all mixins within
an object and applies its internal type, hence it has no remove
method. It implicitly "removes" all mixins that are not among its
own.
You can create a type template like so:
boost::mixin::object_type_template directx_rendering_template; directx_rendering_template .add<game_object>() .add<directx_rendering>() .create();
Again, similar to the case with single_object_mutator
,
you can spread these calls among many blocks or functions.
Don't forget to call create
.
It is the method that creates the internal object type. If you try to apply
a type template that hasn't been created to an object, a runtime error will
be triggered.
To apply a type template to an object, you may pass it as a parameter to its constructor.
boost::mixin::object obj2(directx_rendering_template);
Now obj2
has the mixins
game_object
and directx_rendering
.
Let's create a new type template.
boost::mixin::object_type_template opengl_rendering_template; opengl_rendering_template .add<game_object>() .add<opengl_rendering>() .create();
...to illustrate the other way of applying it to an object:
opengl_rendering_template.apply_to(obj2);
Applying this type template it the same object, was equivalent to mutate
-ing it, removing directx_rendering
and adding opengl_rendering
.
Now we have two objects – obj1
and obj2
– that have
the same mixins.
Sometimes the case would be such that you have a big group of objects that
have the exact same type internally, and want them all to be mutated to have
a different type. Naturally you may mutate
each of them one by one, and this would be the appropriate (and only) way
to mutate a group of objects that have a different type.
If the type is the same, however, you have a slightly faster alternative. The same type mutator:
boost::mixin::same_type_mutator directx; directx .remove<opengl_rendering>() .add<directx_rendering>();
Unlike type templates, same type mutators don't need you to create them explicitly with some method. The creation of the internal type and all preparations are done when the mutation is applied to the first object.
directx.apply_to(obj1); directx.apply_to(obj2);
Remember that the only time you can afford to use a same type mutator, is when all objects that need to be mutated with it are composed of the same set of mixins.
(For the complete, working source of this example see mutation_rules.cpp)
Let's define some mixins that may be present in a CAD system specialized for furniture design. Like in the previous example, we won't concern ourselves with any particular messages.
So, again we have a mixin that we want to be present in every object.
class furniture { int _id; string name; // ... other common fields }; BOOST_DEFINE_MIXIN(furniture, boost::mixin::none);
We also have mixins that describe the frame of the piece of furniture.
class wood_frame {}; BOOST_DEFINE_MIXIN(wood_frame, boost::mixin::none); class metal_frame {}; BOOST_DEFINE_MIXIN(metal_frame, boost::mixin::none);
Let's also define some mixins that will be responsible for the object serialization.
class ofml_serialization {}; BOOST_DEFINE_MIXIN(ofml_serialization, boost::mixin::none); class xml_serialization {}; BOOST_DEFINE_MIXIN(xml_serialization, boost::mixin::none);
And finally let's define two mixins that would help us describe our piece of furniture if it can contain objects inside – like a cabinet or a wardrobe.
class has_doors {}; BOOST_DEFINE_MIXIN(has_doors, boost::mixin::none); class container {}; BOOST_DEFINE_MIXIN(container, boost::mixin::none);
Now, let's move on to the entry point of our program.
We said that each and every object in our system should be expected to
have the mixin furniture
.
That could be accomplished if we manually add it to all mutations we make
but there is a simpler way to do it. By adding the mandatory_mixin
mutation rule.
All mutation rules should be added by calling add_new_mutation_rule
.
Since mandatory_mixin
is
a mutation rule that the library provides, we can accomplish this with
a single line of code:
boost::mixin::add_new_mutation_rule(new boost::mixin::mandatory_mixin<furniture>);
Now each mutation after this line will add furniture
to the objects (even if it's not been explicitly added) and also if a mutation
tries to remove the furniture
mixin from the object, it won't be allowed. There won't be an error or
a warning. The library will silently ignore the request to remove furniture
, or any other mixin that's
been added as mandatory. Note, that if a mutation tries to remove furniture
, and also adds and removes
other mixins, only the part removing the mandatory mixin will be ignored.
The others will be performed.
Another common case for using mandatory_mixin
is if you want to have some debugging mixin, that you want present in you
objects, when you're debugging your application. This is very easily accomplished
if you just set the rule for it in a conditional compilation block.
You probably noticed the mixin ofml_serialization
.
OFML is a format specifically designed for describing furniture that's
still used in some European countries, but hasn't gotten worldwide acceptance.
Let's assume we want to drop the support for OFML, but without removing
the actual code, since third party plugins to our CAD system may still
depend on it. All we want is to prevent anybody from adding the mixin to
an object. Basically the exact opposite of mandatory_mixin
.
This is the mutation rule deprecated_mixin
boost::mixin::add_new_mutation_rule(new boost::mixin::deprecated_mixin<ofml_serialization>);
After that line of code, any mutation that tries to add ofml_serialization
won't be able to, and all mutations will try to remove it if it's present
in an object. Again, as was the case before, if a mutation does many things,
only the part from it, trying to add ofml_serialization
will be silently ignored.
The last built-in rule in the library is mutually_exclusive_mixins
.
Since a piece of furniture has either wood frame or a metal frame and never both, it would be a good idea to prevent the programmers from accidentally adding both mixins representing the frame in a single object. This mutation rule helps us do exactly that.
boost::mixin::mutually_exclusive_mixins* rule = new boost::mixin::mutually_exclusive_mixins; rule->add<wood_frame>(); rule->add<metal_frame>(); boost::mixin::add_new_mutation_rule(rule);
You may add as many mutually exclusive mixins as you wish. If you had,
say, plastic_frame
, you
could also add it to that list.
Any object mutated after that rule is set will implicitly remove any of the mutually exclusive mixins if another is added.
In many of our examples a sample game code was given, with mixins opengl_rendering
and directx_rendering
.
The mutually_exclusive_mixins
is perfect for this case and any other when we're always doing add<x>().remove<y>()
and add<y>().remove<x>()
.
So to see this in practice:
boost::mixin::object o;
This object is empty. Mutation rules don't apply if there's no mutation. If, however, the object had been created with a type template passed in its constructor, then the rules would have been applied.
boost::mixin::mutate(o) .add<ofml_serialization>() .add<xml_serialization>() .add<wood_frame>();
Two rules are affected by this mutation. First it will implicitly add
furniture
to the object,
and second it will ignore the attempt to add ofml_serialization
.
As a result the object will have furniture
,
xml_serialization
and
wood_frame
.
boost::mixin::mutate(o) .add<metal_frame>();
The mutually exclusive mixins will ensure that after this line the object
won't have the wood_frame
mixin.
Having listed all built-in mutation rules, let's define a custom one.
Defining a custom rule is very easy. All you need to do is create a class
derived from boost::mixin::mutation_rule
and override its pure virtual
method apply_to
. The method
has a single input-output parameter – the mutation that has been
requested.
If you remember, we defined two mixins we haven't yet used – has_doors
and container
.
We can safely say that a piece of furniture that has doors is always also
a container (The opposite is not true. Think racks and bookcases). So it
would be a good idea to add a mutation rule which adds the container
mixin if has_doors
is being added, and removes has_doors
if container
is being removed
and the object has doors.
class container_rule : public boost::mixin::mutation_rule { public: virtual void apply_to(boost::mixin::object_type_mutation& mutation) { if(mutation.is_adding<has_doors>()) { mutation.start_adding<container>(); } if(mutation.is_removing<container>() && mutation.source_has<has_doors>()) { mutation.start_removing<has_doors>(); } } };
That's it. Now all we have to do is add our mutation rule and it will be active.
boost::mixin::add_new_mutation_rule(new container_rule); boost::mixin::mutate(o) .add<has_doors>();
After this mutation our custom mutation rule has also added container
to the object.
boost::mixin::mutate(o) .remove<container>();
And after this line, thanks to our custom mutation rule, the object o
will also have its has_doors
mixin removed.
To see all ways in which you can change a mutation from the mutation rule,
check out the documentation entry on object_type_mutation
.
Lastly, there are two important pieces of information about mutation rules that you need to know.
First, note that the library will be responsible for freeing the memory
and destroying the rules you've added. All you need to do is call add_new_mutation_rule
with a rule, allocated
and constructed with new
.
Second, you may have noticed that mutation rules can logically depend on each other. You may ask yourselves what does the library do about that? Does it do a topological sort of the rules? Say we add a mandatory and a deprecated rule about the same mixin. How does it handle dependency loops?
The answer is simple. It doesn't. The rules are applied once per mutation in the order in which they were added. It is the responsibility of the user to add them in some sensible order. Had the library provided some form of rule sort, it would have needlessly overcomplicated the custom rule definition, especially for cases in which you actually want to... well, overrule a rule.
So, that's all there is to know about mutation rules.
(For the complete, working source of this example see combinators.cpp)
For this tutorial let's imagine we have a simple CAD program, which deals designing 3-dimensional objects. In such programs various aspects of an object need to be visible and editable at different times. Let's assume our objects are defined by their wireframe, their vertices, and their surface.
We'll define mixins for those and focus on the parts they have in common: namely whether an part of the object is visible, and how much elements is this part composed of.
BOOST_DECLARE_MIXIN(wireframe); class wireframe { public: void set_visible(bool value); void set_elements_count(int count); bool visible() const; int elements_count() const; // ... private: bool _visible; int _elements_count; }; BOOST_DECLARE_MIXIN(vertices); class vertices { public: void set_visible(bool value); void set_elements_count(int count); bool visible() const; int elements_count() const; // ... private: bool _visible; int _elements_count; }; BOOST_DECLARE_MIXIN(surface); class surface { public: void set_visible(bool value); void set_elements_count(int count); bool visible() const; int elements_count() const; // ... private: bool _visible; int _elements_count; };
Now let's define messages for the methods we'll want to access polymorphically and define our mixins to use those messages.
BOOST_MIXIN_CONST_MULTICAST_MESSAGE_0(bool, visible); BOOST_MIXIN_CONST_MULTICAST_MESSAGE_0(int, elements_count); BOOST_MIXIN_DEFINE_MESSAGE(visible); BOOST_MIXIN_DEFINE_MESSAGE(elements_count); BOOST_DEFINE_MIXIN(vertices, visible_msg & elements_count_msg); BOOST_DEFINE_MIXIN(wireframe, visible_msg & elements_count_msg); BOOST_DEFINE_MIXIN(surface, visible_msg & elements_count_msg);
As you can see those are multicast messages. Each of the mixins in the object will implement and respond to them.
Now let's create some objects.
const int NUM_OBJECTS = 20; boost::mixin::object objects[NUM_OBJECTS]; const boost::mixin::object& o = objects[0];
...and mutate them with some mixins, and give them some values.
For example let's say all of our objects are cubes, that have 24 vertices (4 per side), 6 squares for the wireframe, and a single (folded) surface.
for(int i=0; i<NUM_OBJECTS; ++i) { boost::mixin::object& o = objects[i]; boost::mixin::mutate(o) .add<vertices>() .add<wireframe>() .add<surface>(); o.get<vertices>()->set_elements_count(24); o.get<vertices>()->set_visible(false); o.get<wireframe>()->set_elements_count(6); o.get<wireframe>()->set_visible(false); o.get<surface>()->set_elements_count(1); o.get<surface>()->set_visible(true); }
As you may have noticed, all of our messages are functions that have a return value. You may have tried making multicast messages with non-void functions and noticed that the generated message function is void and doesn't return anything.
The things that will help us make use of the values returned from the messages are the multicast result combinators.
So, let's say we want to see if an object is visible. We say that it is visible
if at least one of its mixins is. To get this value we may use the combinator
boolean_or
provided by the
library (all built-in combinators are in namespace boost::mixin::combinators
)
bool is_first_visible = visible<boost::mixin::combinators::boolean_or>(o); cout << "The first object is " << (is_first_visible ? "visible" : "invisible") << "." << endl;
That's it. Giving a combinator as an explicit template argument to a multicast
message call, will call a function that has a return
value defined by the combinator. In this case boolean_or
causes the message to return a bool
which is true if at least one of the messages returns non-zero.
Had we defined that an object is visible if all of its
mixins were visible, then we could have used the built-in combinator boolean_and
.
Now let's look at another built-in combinator – sum
.
You may have guessed that it's a sum of all values returned by the messages.
In our case we may want to check how many elements are there in an object:
cout << "There are " << elements_count<boost::mixin::combinators::sum>(o) << " elements in the first object." << endl;
All built-in combinators have an alternative usage. You saw the first, where putting the combinator as a template argument, causes the message to return a value.
The second usage keeps the message function void
,
but lets you add the combinator as an output parameter. This way, for example
you may sum all elements throughout all objects with a single reusable combinator:
boost::mixin::combinators::sum<int> sum; for(int i=0; i<NUM_OBJECTS; ++i) { elements_count(objects[i], sum); } cout << "There are " << sum.result() << " elements among all objects." << endl;
That's basically all there is to know about using combinators. Now, let's move on to creating our own custom ones.
The built-in combinators are powerful, but sometimes you need to accomplish a task where you need some specific combinator behavior and need to add a custom one.
To create a custom combinator that's used as an output parameter is very
easy. All you need to do is create a class, that has a public method called
add_result
. This public method
should take one argument of the same type as (or one that can be implicitly
cast to) the return type of the multicast message that you're "combining"
with it. The method will be repeatedly called with each successful message
with its return value as an argument. It should return bool
– true
when the execution
should continue and false
when
it should stop.
We mentioned boolean_or
and
boolean_and
. The function
add_result
in boolean_or
returns false on the first non-zero
value. That means it has determined the the final value is true (because
at least one true has been met) and there is no need to execute the rest.
Likewise boolean_and
's add_result
returns false on the first zero
value it gets. Exactly as C++'s operators ||
and &&
behave.
So let's define our output parameter combinator that counts all mixins that have more than 1 element. Also, we could call it for a single object, but let's make use of the fact that it's an output parameter and count all mixins with more than 1 element among all objects:
struct more_than_1 { more_than_1() : count(0) {} bool add_result(int elements) { if(elements > 1) ++count; // Never break. We need this for all mixins in an object return true; } int count; }; more_than_1 counter; for(int i=0; i<NUM_OBJECTS; ++i) { elements_count(objects[i], counter); } cout << "There are " << counter.count << " mixins with more than 1 element among all objects." << endl;
Now, the last case we need to cover is when you want your custom combinator to be added as a template argument to the message's function giving it a return value of its own.
To do this is only slightly more complicated the the previous return parameter case.
You need to create a template class whose template parameter will be provided by the message call and will be the message return type.
Next, as before you'll need an add_result
method to be repeatedly called, again having a single argument of type equal
to the message return type (you may just reuse the template argument of the
combinator class), and again returning bool
to indicate whether the message execution should continue or stop.
Next, you'll need a typedef result_type
,
which will indicate the return type of the message function.
Lastly, you'll have to create a method, called result, with no arguments,
that has a return type result_type
.
It will be called when the execution is completed and it will provide the
return value of the message function.
So, let's create an identical combinator as before – one that counts the mixins in an object that have more than one element, but this time to be used as a template argument of the message function.
template <typename MsgReturnType> struct more_than_1_t { more_than_1_t() : count(0) {} bool add_result(MsgReturnType elements) { if(elements > 1) ++count; // Never break. We need this for all mixins in an object return true; } // return type of message function typedef int result_type; result_type result() const { return count; } int count; };
Now we can use our new custom combinator as we used boolean_or
above.
cout << "There are " << elements_count<more_than_1_t>(o) << " mixins with more than 1 element in the first object." << endl;
And that's all there is about multicast result combinators.
#define
transform_messages set_position_msg
& set_orientation_msg
.
Then use it like this BOOST_DEFINE_MIXIN(x, some_messages
& transform_messages);
#define
BM_C_MSG_1 BOOST_MIXIN_CONST_MESSAGE_1
object_type_template
-s
instead of mutating each new object in the same fashion.
same_type_mutator
when mutating multiple objects of the same type.
boost::mixin::object&
.
They will be indistinguishable from messages.
bool
and then use the boolean_or
combinator. It will stop
the message execution on the first true
.
Sometimes you will feel the need to have mixins with a common parent. Most
likely this will happen when you want to define two different mixins that
share some common functionality. Moving the shared functionality in the
same common parent is a good idea and Boost.Mixin will work exactly the
same way if you do this. However there is a pitfall in this case. It happens
when you have multiple inheritance. Due to the special nature in which
the library arranges the memory internally, if a mixin type has more than
one parent, using bm_this
in some of those parents might lead to crashes.
More precisely, when the library allocates memory for a mixin type, it
allocates a buffer that is slightly bigger than needed and puts the pointer
to the owning object at its front. What bm_this
does is actually an offset from this
with the appropriate number of bytes for object*
. So if a parent of your mixin type, other
than the first, calls bm_this
,
it will end up returning an invalid pointer to the owning object.
To be able to have parents, other than the first, with access to the owning object we suggest that you create a pure virtual function that gets it from the actual mixin type.
Say virtual object* get_bm_object() = 0;
in the parents, which is implemented in
the child class (the actual mixin defined with BOOST_DEFINE_MIXIN
)
by simply return bm_this
.
Of course there are other ways to accomplish this, for example with CRTP, but the virtual function is probably the cleanest and safest one.
_boost_get_mixin_type_info
supports the type
object::get
or object::has
.
BOOST_DECLARE_MIXIN
)
_boost_get_mixin_feature
supports the type
object::implements
with a message that cannot be recognized as such.
_msg
suffix to the message.
_boost_register_mixin_feature
supports the type
&
supports the type
BOOST_DEFINE_MIXIN
that cannot be recognized as such
_msg
suffix. If it's an allocator,
make sure it's derived from the mixin_allocator
class
BOOST_MIXIN_DEFINE_MESSAGE
that have same name in the same file.
BOOST_MIXIN_xxx_MESSAGE_N_OVERLOAD
.
Define the overloads.
Undefined reference (mentioned below) will be reported as an "unresolved external symbol" in Visual C++.
_boost_mixin_get_type_info
BOOST_DEFINE_MIXIN
BOOST_DECLARE_EXPORTED_MIXIN
_boost_register_mixin_feature
and _boost_get_mixin_feature
BOOST_MIXIN_DEFINE_MESSAGE
BOOST_MIXIN_EXPORTED_xxx_MESSAGE_N
_boost_register_mixin_feature
and _boost_get_mixin_feature
BOOST_MIXIN_xxx_MESSAGE_N_OVERLOAD
The exceptions the library may throw, what causes them, and how to fix them can be found in the reference. Besides them, these may also occur:
domain.hpp
:
"you have to increase the maximum number of mixins"
BOOST_MIXIN_MAX_MIXINS
in the file config.hpp
of the library and then rebuild it.
domain.cpp
:
"you have to increase the maximum number of messages"
BOOST_MIXIN_MAX_MESSAGES
in the file config.hpp
of the library and then rebuild it.
[1]
Although set_mesh is a message that can be handled by a single class in
our example, in an actual product there would be other types of "model"
mixins, which would make it polymorphic. That's why we're making it a message
instead of a method to be called by object.get<animated_model>()->set_mesh(somemesh)