Boost logo

Boost :

Subject: Re: [boost] [mixin] New library reminder
From: Klaim - Joël Lamotte (mjklaim_at_[hidden])
Date: 2013-07-13 05:31:41


On Sat, Jul 13, 2013 at 9:20 AM, Borislav Stanimirov <b.stanimirov_at_[hidden]>wrote:

> 2. did you avoid using a smart pointer in the introduction example for a
>> specific reason? if not, I think it would be better to use one,
>> just to avoid people copying this style of code.
>>
>>
> Well I didn't want to overburden the reader with the notion of a smart
> pointer. The messages' first argument is `boost::mixin::object*` so using a
> smart pointer would also require the constant use of `ptr.get()` to call
> messages.
>
>

I don't agree on the overhead of smart pointers because if a C++ dev don't
know what it is, it would be time to learn about it (even if he don't use
it in the end).
However I agree that using a specific smart pointer type might be
misleading.
So I suggest using auto and a factory function to abstract the example.
Even if someone don't have access to auto, he can at least understand that
it's just
some kind of pointer.

> This makes me think, though. I'll have to think about making the first
> argument a reference to an object instead a pointer to an object, or allow
> both (with the cost of an additional copying of the actual arguments).
>
>

Indeed, it would make things safer and clearer. Here is how it would look
like in the end:

using namespace boost::mixin;
auto o = create_new_object(); // just a smart pointer to an empty object

// xml_serializer and book_data are some classes in the project
// there are external macros you need to call, to make them available as
// mixins
mutate(*o)
    .add<xml_serializer>()
    .add<book_data>();

// now o has internally instantiated an xml_serializer and book_data
// it can be thought of a class that's been derived from them via
// multiple inheritance

// ...
set_title(*o, "The Three Musketeers"); // this will internally call
                                      // book_data::set_title
serialize(*o); // this will internally call xml_serialier::serialize

mutate(*o)
    .remove<xml_serializer>()
    .add<json_serializer>();

// now o has destroyed its xml_serializer instance and added a
// json_serializer instead

serialize(*o); // will now call json_serializer::serialize

But that don't prevent you from providing overloads that take
pointer-semantic types too!
Not totally sure it's a good idea though.

>
> 3. is there a way to go through all the instances of the same type? for
>> example:
>> mutate(o)
>> .add<xml_serializer>()
>> .add<book_data>();
>>
>> Assuming there is more than one instance composed of book_data, is there a
>> way to list
>> or just apply a function to all currently instanciated book_data?
>>
>> If not, I think a way to do it would be useful in high performance cases
>> (to update all the components of the
>> same type in one pass, avoiding cache misses and enabling some possible
>> concurrent update setup).
>> But as it's not the core feature I think it would be a nice update.
>>
>>
> The library doesn't provide such a feature. There are many axes by which
> the memory is accessed, so completely avoiding cache misses is impossible.
> Indeed, there are ways to improve on the cache locality on this or that
> axis, but that will be at the cost of worsening it on another.
>
> Internally the object separately instantiates the mixins that comprise it
> and it effectively has a list of pointers to its mixins. If you mutate that
> object, removing or adding mixins, all of its other mixins, that are
> already instantiated, won't be touched. To keep all the mixins in a single
> buffer will require either dealing with big gaps and potentially badly
> fragmented memory, or moving the mixin data in the memory, which can be
> slow and WILL break any pointers that reference it (especially bad for
> classes that have self referencing in some way, like, say
> google::hash_map/hash_set).
>
> A mixed approach CAN be introduced, but that will require the library
> users to have deep knowledge of its internal workings in order to choose
> how their mixins will behave, and that might alienate some users.
>
> To deal with the problem of updating a huge list of objects of the same
> internal type (or memory problems in general) two things are introduced in
> the library:
>
> * Object mutators - that speed up mutation by remembering what needs to be
> done and greatly improve on the performance of the naive
> `mutate(obj).add.add.add`
> * Memory allocators that can be set for all mixin allocations or also for
> the allocations of specific mixins
>
> Still it is the user's job to maintain lists of the objects by whatever
> criteria they choose (like object that have `book_data` in them). Sure, the
> library could easily store all kinds of lists by all kinds of criteria, but
> I fear that this will be memory wasted, since the users know best what and
> WHETHER to specifically maintain.

>From what I understand, the allocator might be enough to allow the user of
the library to set up a pool or dynarray or non-ressizing vector of
components of the same type,
which would be the feature I'm asking for.
I'm not sure though. Would it be problematic to allow the user to provide a
factory function for the component on addition, so that there is a simple
way to
make sure the component is created the way we want?

Something like:

    mutate(o).add( [&]{ return world_components.create<book_data>() ); //
template parametter types implicitely inferred
    // the added data_book is allocated in an array (or stable vector or
pool or...)
    // here add would take a unique_pointer or similar - or maybe a second
function for deletion - or maybe an object providing both, like an alocator
but dynamic.

Which then would allow something like:

world_components.for_all<book_data>( [&]( book_data& data ){ data.update(
info ); } ); // go through the array of book_data to update them all

What I fear is that the allocator would not be enough to allow different
pool/list/whatever of these components to be defined,
for different "contexts". I have a case where I need different "worlds" to
exists in parallel and components are gathered in type-specific (stable)
arrays.

I'm asking because it's a setup pretty common in recent game engine
development (the variant being based on ids instead of pointers - but it's
totally different implementation)
and your mixin library is very close to it, so it feels helpful.
The general normal case like in the example would be the default as it is
not always necessary to have such kind of setup, it's mostly for speed and

I think this add() overload would be a simple addition but would allow the
user to set the combined elements as he wishes.
I'm not totally sure of the best interface for it though.

Joel Lamotte


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk