Search code examples
c++strict-aliasing

Aliasing for small buffer optimization with std::aligned_union and std::aligned_union


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 of Align

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?


Solution

  • 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));