在类型擦除的小对象优化中调试崩溃

Debugging crash in small object optimization for type erasure

我正在实现一个 class 来对小对象执行类型擦除,但遇到了一个我不理解的分段错误。

以下程序:

#include <iostream>
#include <type_traits>

struct small_object
{
  public:
    template<class T>
    small_object(const T& value)
    {
      new(&storage_) concrete<T>(value);
    }

    ~small_object()
    {
      get_abstract().~abstract();
    }

    void print() const
    {
      // XXX crash here
      get_abstract().print();
    }

  private:
    struct abstract
    {
      virtual ~abstract(){}

      virtual void print() const = 0;
    };

    template<class T>
    struct concrete
    {
      concrete(const T& value) : value_(value) {}

      void print() const
      {
        std::cout << value_ << std::endl;
      }

      T value_;
    };

    abstract& get_abstract()
    {
      return *reinterpret_cast<abstract*>(&storage_);
    }

    const abstract& get_abstract() const
    {
      return *reinterpret_cast<const abstract*>(&storage_);
    }

    typename std::aligned_storage<4 * sizeof(void*)> storage_;
};

int main()
{
  small_object object(13);

  // XXX i expect this line to print '13' to the terminal but it crashes
  object.print();

  return 0;
}

XXX 指示的行崩溃。

我认为问题是对 .print() 的虚拟调用没有被正确地动态调度,但我不明白为什么。

谁能告诉我我错过了什么?

您没有从 abstract 派生 concrete<T>,因此当您使用放置 new 构造对象时没有创建 vtable。因此,当您尝试调用虚函数时,它会失败; concrete<T>abstract 在这个例子中实际上是完全不相关的类型。

如果您使用的是 C++11 或更高版本,我建议您使用 override 关键字,以允许编译器在这种情况下生成错误。

std::aligned_storage<4 * sizeof(void*)> storage_;

这将创建一个字节的存储空间

模板参数不设置声明对象的大小,而是设置可以分配到该类型大小合适的数组中的对象的大小。因此,你需要

std::aligned_storage<4 * sizeof(void*)> storage_[4 * sizeof(void*)];

GCC 6.2.0 警告您:

warning: placement new constructing an object of type ‘small_object::concrete<int>’ and size ‘16’ in a region of type ‘std::aligned_storage<32ul>’ and size ‘1’ [-Wplacement-new=]

(您仍然需要从 abstract 导出 concrete)。