I was looking at implementation small buffer optimization for a std::function
-like object.
Boost implements the small buffer for boost::function
like this:
union function_buffer
{
mutable void* obj_ptr;
struct type_t {
const detail::sp_typeinfo* type;
bool const_qualified;
bool volatile_qualified;
} type;
mutable void (*func_ptr)();
struct bound_memfunc_ptr_t {
void (X::*memfunc_ptr)(int);
void* obj_ptr;
} bound_memfunc_ptr;
struct obj_ref_t {
mutable void* obj_ptr;
bool is_const_qualified;
bool is_volatile_qualified;
} obj_ref;
// To relax aliasing constraints
mutable char data;
};
and does things like:
new (reinterpret_cast<void*>(&out_buffer.data)) functor_type(*in_functor);
Moreover C++11 provides, std::aligned_union
and std::aligned_storage
which seems suitable for this. The former gives a type:
suitable for used as uninitialized storage for any object whose size is at most
Len
and whose alignment is a divisor ofAlign
I'd be tempted to use something like:
class MyFunction {
private:
typename std::aligned_storage<something>::type buffer;
MyFunctionVtable* vtable;
public:
template<class F>
MyFunction(F f)
{
static_assert(sizeof(F) <= sizeof(buffer) &&
alignof(F) <= alignof(buffer), "Type not suitable");
new (&buffer) F(std::move(f));
vtable = ...;
}
// [...]
};
Don't this (or the boost implementation) break the type aliasing rules and why? I'd tend to thinks there are pitfalls which would trigger undefiend behaviour.
For reference, a note in the C++ standard gives an typical implementation of aligned_storage
:
template <std::size_t Len, std::size_t Alignment>
struct aligned_storage {
typedef struct {
alignas(Alignment) unsigned char __data[Len];
} type;
};
which looks similar to the boost version in the sens that both rely of char
to “enable” aliasing.
What about std::aligned_union<>::type
? Is is safe to use a type which is not explicitly listed?
To a first approximation, aliasing occurs when you write to a storage location with one type and read with another, and neither of those types are a narrow character type (char
, signed char
, unsigned char
).
The Boost implementation is unsafe if it is at any point writing to function_buffer
with one member and then reading another member, unless one of those members is data
. The fact that the data
member is commented // To relax aliasing constraints
might indicate that the Boost developers believe that they can trick the compiler into not noticing an aliasing violation.
Your proposed solution of std::aligned_storage
or std::aligned_union
is a good one, as long as your vtable
only reads via the type that was used when writing in your placement-new expression new (&buffer) F(std::move(f));
, so it's fine to write reinterpret_cast<F*>(&buffer)
and use the resulting expression as an object of type F*
pointing to an object of type F
.
With std::aligned_union
it's fine to placement-new any type with lesser size and alignment requirements. It would usually be a good idea to make this explicit with a static_assert:
static_assert(sizeof(F) <= sizeof(buffer));
static_assert(alignof(F) <= alignof(buffer));
// OK to proceed
new (&buffer) F(std::move(f));