HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Yet another 'any' class implementation, named 'some'

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
anynamedyetanothersomeimplementationclass

Problem

This is a follow-up of 'any' class implementation. After posting my answer, I kept working on the code towards parameterizing with respect to memory management as I had promised. I ended up in some pretty non-trivial piece of code that I decided to post here as a new question for review.

Basically, this is quite close to boost::any, with a number of differences. From my perspective it could be a complete replacement to boost::any, that I like to call some.

Minor differences:

-
Member-function, reference (not pointer)-based, type-checked/-unchecked data access (casting).

-
Simplified type-checked access via dynamic_cast instead of manual typeid checking. Using std::bad_cast instead of a custom exception type.

-
Type provided for casting should be already decayed; no references removed.

-
Conversion operators.

-
Free of const_cast hacks.

-
No type() functionality exposed; is() check interface instead. Internally, custom type identification mechanism bypassing typeid and RTTI.

-
Move semantics fully supported. No constraints e.g. on reading from const rvalue references or providing non-const rvalue references to temporary objects.

-
Using empty base optimization, empty objects are stored with no space overhead on top of the required virtual function table.

Major differences:

-
Templated on a memory-management object that is roughly what an allocator is for STL containers, yet with a different, custom interface.

-
Default "allocator" type provides customizable storage space on stack. Similarly to small string optimization, and without run-time overhead, objects not larger than this space are placed on stack; larger ones on the free store.

So some is maybe somewhere between boost::any and boost::variant (in using the stack rather than restricting to pre-defined types). To get a rough idea about this "allocator", here is what it would look like if it used the free store only:

```
struct alloc
{
template
D *

Solution

The very idea of the storage inside some made me think about the problems it brings in swap() and assign(). I was first thinking about adding the swap() as another virtual method, possibly even the assignment. That could help you solve the problem in a bit different way - you will know if you are moving the pointer or need to copy/move the object from one embedded storage to another.

But on second thought, the optimization behind the scene could as well be solved with good allocator for small objects utilizing some free-list and spinlocks or some other lockfree technique - e.g. boost::pool or my own same-size allocator (very old school project). That will help you a lot with the problem of exceptions in swap/assign, without the need for temporary, try..catch and repair.

If you still think about using the storage, I would not inherit it in some, but in inner class containing the pointer first (one base class having the pointer) and the store next (second base class or composition, which would be better regarding next paragraph). This could speed it a bit more by making it more sequential on reading (pointer first, vmtptr next, data last). You could as well move the vmtptr next to data pointer (no matter the storage), with few more tricks with placement new (separating virtual methods from the data and a bit terrible hack to access the data pointer next to this as you know the layout). I wouldn't write this not seeing you trying to optimize it with the embedded storage, which itself is overkill - std::allocator should handle this - I still think that solving this (fast allocation of tiny objects) will render your embedded storage as not needed (and possibly helping other containers on the way).

The allocator seems more like a feature of some, than allocator or usable extension/option for it. I would personally not expose it and make some a simple class (not a template). Or pass a real (but universal) allocator, something like std::allocator, with allocate and deallocate optimized for tiny blocks. You could as well use rebind for all the types.

The Alloc/Store Interface

  • copy accepts universal (rvalue) reference and therfore looks like to be something else, but from the code I can see you use it on one and only place - data::copy which uses it with simple (lvalue) reference. I think that it would be a good idea to change the signature to match it (otherwise it may became a sort of move instead of copy). Actually, it mimics construct(allocate(1), v).



  • move seems to be there to move data from one storage to another (either embeded or heap). It looks to be doing what it was designed for, but I would change the inner copy to new(space) ..., to make it more clear (and understandable).



  • free is good, it means destroy + possibly dispose.



  • swap is something I would definitelly add (using std::swap) to help you solve the problems you have in some::swap() - I would just copy the noexcept from it and remove try..catch. It simply should not throw and you can take advantage of std::swap and possible specializations (using std::swap; swap(x,y) - you may look there for copy-and-swap to get some more related info).



Author: Expression fits() is evaluated at compile time, so whenever it evaluates to false, store is equivalent to alloc, without any run time overhead. For N = 0, it is always equivalent (or I think so, right?).

For me it looks to be related to this:

template
class store
{
    char space[N];

    template
    static constexpr bool
    fits() { return sizeof(typename std::decay::type) <= N; }


sizeof will never return negative value, and zero-size array at the end of structure is allowed from what I know (at least good working extension I have used few times). If you run into problems, you can simply specialize the template (for N = 0).

Now it seems I have nothing to add, unless I see some feedback ;)

Code Snippets

template<size_t N = 16>
class store
{
    char space[N];

    template<typename T>
    static constexpr bool
    fits() { return sizeof(typename std::decay<T>::type) <= N; }

Context

StackExchange Code Review Q#48344, answer score: 7

Revisions (0)

No revisions yet.