在小对象优化中调试崩溃以进行类型擦除

Debugging crash in small object optimization for type erasure

本文关键字:类型 擦除 崩溃 调试 对象 优化      更新时间:2023-10-16

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

以下程序:

#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_;

这将创建一个字节的存储。

template 参数不设置声明对象的大小,而是设置可在适当大小的此类数组中分配的对象的大小。因此,您需要

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

GCC 6.2.0 会就此发出警告:

警告:placement new在类型为 ' std::aligned_storage<32ul>' 且大小为 ' 1' 的

区域中构造类型为 ' small_object::concrete<int>' 且大小为 ' 16' 的对象 [-Wplacement-new=]

(您仍然需要从abstract派生concrete)。