Сбой отладки при оптимизации малых объектов для стирания типов


Я реализую класс, который выполняет стирание типов для небольших объектов и столкнулся с ошибкой сегментации, которую я не понимаю.

Следующая программа:

#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 2

2 ответа:

Вы не производили concrete<T> от abstract, поэтому при построении объекта с помощью placement new не создается vtable. Таким образом, при попытке вызвать виртуальную функцию она завершится неудачей; concrete<T> и abstract на самом деле являются совершенно несвязанными типами в этом примере.

Я бы рекомендовал использовать ключевое слово override, Если вы используете C++11 или новее, чтобы позволить компилятору генерировать ошибку в подобных случаях.

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

Это создает хранилище одного байта.

Аргумент template задает не размер объявленного объекта, а размер объекта, который может быть выделен в массиве соответствующего размера этого типа. Следовательно, вам нужно
std::aligned_storage<4 * sizeof(void*)> storage_[4 * sizeof(void*)];

GCC 6.2.0 предупреждает вас об этом:

Предупреждение: placement new построение объекта типа 'small_object::concrete<int> 'и размера' 16’ в области типа 'std::aligned_storage<32ul> 'и размера' 1’ [- Wplacement-new=]

(вам все еще нужно выведите concrete из abstract).