<snip> big thread about polymorphic container ... </snip>
I hope I didn't miss anything in the thread. If I'm repeating something,
sorry.
I don't think we want or need to limit what we put into the container.
Neither by forcing certain virtual functions onto the base class, nor by
limiting what the contained types can and can't do (like have pointers to
themselves and do funky things in their copy/move constructors).
When push_back<Triangle> is called, we create an instance of an
ItemHandler<Triangle> class, that derives from BaseItemHandler, and
implements copy/move virtually. ie type-erasure. ItemHandler<Triangle>
knows how to move/copy Triangles. It is not Triangle's job!
We put the ItemHandler<Triangle> in a map.
Note:
- we only need *one* ItemHandler<Triangle> for all Triangles, not one for
each.
- Triangle doesn't have any do_copy/move/size virtual functions,
ItemHandler<Triangle> does.
- we can store Triangle mixed in with Squares and other shapes or isolated
in a group of Triangles. Maybe different containers will choose
differently.
- if stored mixed together, then we have a parallel vector that is a
pointer to BaseItemHandler* for each item in the main vector.
ie (typed in email, so should not compile)
struct BaseItemHandler
{
virtual void do_copy(char * dest, char const * src) const = 0;
virtual std::size_t size() const = 0;
};
template <typename T>
struct ItemHandler : BaseItemHandler
{
virtual void do_copy(char * dest, char const * src) const
{
T * typed_src = reinterpret_cast(src);
new (dest) T(*typed_src); // in-place copy construct
}
virtual std::size_t size() const
{
return sizeof(T);
}
};
template <typename Item> void classifier::push_back(Item const & item)
{
char const * key = typeid(Item).name(); // or use raw name or address
of type info
// add/get handler to/from map
HandlerMap::iterator itHandler = handlerMap.find(key);
BaseItemHandler * handler = 0;
if (itHandler == handlerMap.end()) {
// new type we haven't seen before
handler = new ItemHandler<Item>;
handlerMap[key] = handler;
} else {
handler = *itHandler;
}
char * space = make_space_for_item(handler->size()); // make space in
vector<char>
handler->do_copy(space, item); inplace copy construct
handlerList.push_back(handler); // handler for this item
// note that handler is in handlerMap *once* (ie one per type)
// and same pointer may be in handlerList multiple times, as it appears
once for each item of type Item
}
classifier::iterator & classifier::iterator::operator++(int)
{
// iterator has pointer/iterator/offset into handlerList (called
'itHandler')
BasItemHandler * handler = *itHandler;
// and pointer/iterator/offset into vector<char>, called 'data'
// ++ on data
data += handler->size();
// ++ on handler
++itHandler;
// iterator now points to next object in the vector,
// and points to its associated handler
return *this;
}
Tony
On Tue, Aug 27, 2013 at 8:09 AM, Larry Evans wrote:
On 08/27/13 06:44, Thorsten Ottosen wrote:
On 23-08-2013 17:08, Larry Evans wrote:
AFAICT, this shows the offset_ptr would solve the problem of moving
the storage in case the storage needs to be resized.
Am I missing something?
I can't tell. Some interprocess experts might want to way in here?
Anyway, I can live with a container that has unstable
pointers/references after push_back. std::vector<T> is like that,
so what's exactly the difference here?
The offset_ptr solves the unstable
pointer problem caused by moving the storage buffer.
That's one problem 1st mentioned by Rob Stewart here:
http://article.gmane.org/gmane.comp.lib.boost.devel/243582
and then again by Ben Pope here:
http://article.gmane.org/gmane.comp.lib.boost.devel/243633
The code I last posted shows the offset_ptr still points to
the *this even after the move, although the raw pointer
points to the old location. Also, no virtual methods are
needed to preserve this stable pointer feature. Using
just offset_ptr seems to do the job.
-regards,
Larry
_______________________________________________
Unsubscribe & other changes:
http://lists.boost.org/mailman/listinfo.cgi/boost