patterncppMinor
Function wrapper like std::function that uses "small buffer" allocation
Viewed 0 times
stdfunctionlikeuseswrapperthatsmallallocationbuffer
Problem
My
I would like a review for:
Here is my code:
```
#include
#include
#include
#include
#include
#include
template
struct Function;
template
struct Function
{
Function() : storage()
{}
template
Function(const F& f)
{
static_assert(alignof(F) (f);
}
Function(const Function& rhs) :
storage(rhs.storage)
{
if (rhs.valid())
{
rhs.getImpl().clone(data());
}
}
Function(Function&& rhs) noexcept :
storage(rhs.storage)
{
rhs.storage = Storage();
}
Function& operator=(Function rhs) noexcept
{
std::swap(storage, rhs.storage); // not sure if safe
return *this;
}
~Function()
{
if (valid())
{
getImpl().~Base();
}
}
R operator()(Args&& ...args) const
{
if (!valid())
{
throw std::bad_function_call();
}
return getImpl().call(std::forward(args)...);
}
private:
struct Base
{
virtual ~Base() {}
virtual R call(Args&& ...args) const = 0;
virtual void clone(void* where) const = 0;
};
template
struct Impl : Base
{
Impl(const F& f) : f(f) {}
R call(Args&& ...args) const override final
{ return f(std::forward(args)...); }
void clone(void* where) const override final
{ new (where) Impl(*this); }
F f;
};
// convenience methods
bool valid() const
{ return storage != Storage(); }
const void* data() const
{ return static_cast(storage.data()); }
void* data()
{ return static_cast(storage.data()); }
const Base& getImpl() con
Function class is similar to std::function with the small-buffer optimization. However, it always uses a small buffer and static asserts if the required size exceeds it.I would like a review for:
- correct use of the low-level features
- general class design and interoperability with standard library
Here is my code:
```
#include
#include
#include
#include
#include
#include
template
struct Function;
template
struct Function
{
Function() : storage()
{}
template
Function(const F& f)
{
static_assert(alignof(F) (f);
}
Function(const Function& rhs) :
storage(rhs.storage)
{
if (rhs.valid())
{
rhs.getImpl().clone(data());
}
}
Function(Function&& rhs) noexcept :
storage(rhs.storage)
{
rhs.storage = Storage();
}
Function& operator=(Function rhs) noexcept
{
std::swap(storage, rhs.storage); // not sure if safe
return *this;
}
~Function()
{
if (valid())
{
getImpl().~Base();
}
}
R operator()(Args&& ...args) const
{
if (!valid())
{
throw std::bad_function_call();
}
return getImpl().call(std::forward(args)...);
}
private:
struct Base
{
virtual ~Base() {}
virtual R call(Args&& ...args) const = 0;
virtual void clone(void* where) const = 0;
};
template
struct Impl : Base
{
Impl(const F& f) : f(f) {}
R call(Args&& ...args) const override final
{ return f(std::forward(args)...); }
void clone(void* where) const override final
{ new (where) Impl(*this); }
F f;
};
// convenience methods
bool valid() const
{ return storage != Storage(); }
const void* data() const
{ return static_cast(storage.data()); }
void* data()
{ return static_cast(storage.data()); }
const Base& getImpl() con
Solution
Generally, I can't see any real problems, but there are a few things that I'm going to point out:
In the assignment operator, you use
The only other issues I can see are to do with alignment and storage size, which you've basically dealt with thanks to the two
With regards to alignment, I'm less confident.
which (as far as I can see) should ensure storage will be aligned on the correct boundary.
In the assignment operator, you use
std::swap(storage, rhs.storage) and declare it as noexcept. This should probably be conditionally noexcept, based on whether the swap implementation for the given array type (in this case, array) is noexcept. That being said, I'd be really, really surprised if this wasn't always true.The only other issues I can see are to do with alignment and storage size, which you've basically dealt with thanks to the two
static_asserts in the constructor for Function (although they should both have a string detailing exactly what happened if either of them fails). As you're probably aware, there are precious few guarantees about function pointer sizes (especially when you start dealing with a combination of member functions, virtual functions, and multiple inheritance). 4 * sizeof(long) should be enough space to hold anything like this, but this is implementation defined; there are no guarantees.With regards to alignment, I'm less confident.
long is only guaranteed by the standard to be at least 4 bytes, and this is true even for 64-bit platforms. Since storage is actually holding the function itself, the alignment should be made more explicit, and shouldn't rely on the (potentially less stringent) alignment of long. I'd personally suggest:alignas(std::intptr_t) Storage storage;which (as far as I can see) should ensure storage will be aligned on the correct boundary.
Code Snippets
alignas(std::intptr_t) Storage storage;Context
StackExchange Code Review Q#58447, answer score: 6
Revisions (0)
No revisions yet.