patterncppMinor
Yet another 'any' class implementation, named 'some'
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
Minor differences:
-
Member-function, reference (not pointer)-based, type-checked/-unchecked data access (casting).
-
Simplified type-checked access via
-
Type provided for casting should be already
-
Conversion operators.
-
Free of
-
No
-
Move semantics fully supported. No constraints e.g. on reading from
-
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
```
struct alloc
{
template
D *
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
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,
If you still think about using the storage, I would not inherit it in
The allocator seems more like a feature of
The Alloc/Store Interface
Author: Expression
For me it looks to be related to this:
Now it seems I have nothing to add, unless I see some feedback ;)
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
copyaccepts 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::copywhich 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 mimicsconstruct(allocate(1), v).
moveseems 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 innercopytonew(space) ..., to make it more clear (and understandable).
freeis good, it means destroy + possibly dispose.
swapis something I would definitelly add (using std::swap) to help you solve the problems you have insome::swap()- I would just copy thenoexceptfrom it and removetry..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.