使用 std::aligned_union 和 std::aligned_union 的小缓冲区优化别名

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

我正在研究 std::function 类对象的小缓冲区优化实现。

Boost 像这样为 boost::function 实现小缓冲区:

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;
  };

并做如下事情:

new (reinterpret_cast<void*>(&out_buffer.data)) functor_type(*in_functor);

而且C++11提供了std::aligned_union。前者给出类型:

suitable for used as uninitialized storage for any object whose size is at most Len and whose alignment is a divisor of Align

我很想使用类似的东西:

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 = ...;
  }
  // [...]
};

这(或 boost 实现)是否违反了类型别名规则?为什么?我倾向于认为存在会引发不守规矩行为的陷阱。

作为参考,C++ 标准中的注释给出了 aligned_storage:

的典型实现
template <std::size_t Len, std::size_t Alignment>
struct aligned_storage {
  typedef struct {
    alignas(Alignment) unsigned char __data[Len];
  } type;
};

从某种意义上说,这看起来与提升版本相似,两者都依赖 char 来“启用”别名。

std::aligned_union<>::type呢?使用未明确列出的类型是否安全?

首先,当您使用一种类型写入存储位置并使用另一种类型读取时,会发生别名,并且这些类型都不是窄字符类型char, signed char, unsigned char).

如果 Boost 实现在任何时候用一个成员写入 function_buffer 然后读取另一个成员,除非其中一个成员是 data,否则它是不安全的。 data 成员被注释为 // To relax aliasing constraints 的事实可能表明 Boost 开发人员认为他们可以欺骗编译器不注意别名冲突。

您提出的 std::aligned_storagestd::aligned_union 解决方案是一个很好的解决方案,只要您的 vtable 仅通过写入 placement-new 表达式时使用的类型进行读取new (&buffer) F(std::move(f));,所以可以写成reinterpret_cast<F*>(&buffer),并将结果表达式作为一个F*类型的对象,指向一个F.

类型的对象

使用 std::aligned_union 可以放置新的具有较小尺寸和对齐要求的任何类型。用 static_assert:

明确表示通常是个好主意
static_assert(sizeof(F) <= sizeof(buffer));
static_assert(alignof(F) <= alignof(buffer));
// OK to proceed
new (&buffer) F(std::move(f));