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

时间:2017-01-06 21:00:11

标签: c++ virtual-functions type-erasure

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

以下计划:

#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()的虚拟呼叫没有正确动态调度,但我不明白为什么。

谁能说出我错过了什么?

2 个答案:

答案 0 :(得分:4)

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

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

答案 1 :(得分:2)

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

这会创建一个字节的存储

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

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

GCC 6.2.0警告你:

  

警告:placement new在类型为“small_object::concrete<int>”且尺寸为“{{1}”的区域内构建类型为“16”且尺寸为“std::aligned_storage<32ul>”的对象}'[ - Wplacement-new =]

(您仍然需要从1)派生concrete