获取没有对象

时间:2016-03-04 16:48:50

标签: c++ c++11 g++ undefined-behavior vptr

我正在尝试实施类似于the first described here的系统。也就是说,(ab)使用vtable修改来改变运行时的对象行为。这是我尝试在我正在开发的C ++项目中创建高效类型通用包装器的一部分。

该示例,如果您无法访问它,请使用memcpy()this指针复制vtable:

void setType( const DataType& newType )
{
    memcpy( this, &newType, sizeof(DataType) );
}

但是,我对此方法存在一个问题:我没有目标类的对象来复制vtable,也不想创建一个,因为某些类型的构造代价很高。

有没有办法访问vtable,它将被放置到给定类的对象中而没有该类的对象?

如果它在某种程度上是可移植的,那将是更好的,但我基本上已经辞职,因为它是特定于编译器的;因此,如果没有其他选项,只接受GCC / G ++方法。我们还假设我只关心在相当标准的操作系统和体系结构上构建它。

我正在使用C ++ 11,应该以某种方式为此提供帮助。

编辑:我想要完全清楚,我知道这种行为是多么危险。尽管我的介绍可能有所建议,但我对这个想法更感兴趣,也许它在非常有控制的环境中应用它,而不是我对生产软件的好主意。

1 个答案:

答案 0 :(得分:4)

struct many_vtable {
  void(*dtor)(void*);
  void(*print)(void const*,std::ostream&);
};
template<class T>struct tag_t{};
template<class T>constexpr tag_t<T> tag = {};

template<class T>
many_vtable const* make_many_vtable(tag_t<T>){
  static const many_vtable retval = {
    // dtor
    [](void* p){
      reinterpret_cast<T*>(p)->~T();
    },
    // print
    [](void const*p, std::ostream& os){
      os<<*reinterpret_cast<T const*>(p);
    }
  };
  return &retval;
}
struct many {
  many_vtable const* vtable=nullptr;
  std::aligned_storage_t<100, alignof(double)> buff;
  void clear(){if(vtable) vtable->dtor(&buff);vtable=nullptr;}
  ~many(){ clear(); }
  many()=default;
  many(many const&)=delete; // not yet supported
  many& operator=(many const&)=delete; // not yet supported

  explicit operator bool()const{return vtable!=nullptr;}

  template<class T,class...Args>
  void emplace(Args&&...args){
    static_assert(alignof(T) <= alignof(double), "not enough alignment");
    static_assert(sizeof(T) <= 100, "not enough size");
    clear();
    ::new((void*)&buff) T(std::forward<Args>(args)...);
    vtable=make_many_vtable(tag<T>);
  }
  friend std::ostream& operator<<(std::ostream& os, many const&m){
    if(!m.vtable) return os;
    m.vtable->print(&m.buff, os);
    return os;
  }
};

This is a manual vtable design. It can store anything up to 100 bytes whose alignment is less than a double that can be printed to a stream.

'Erasing' down to more (or different) operations is easy. For example, being able to copy/move to another many.

It does not violate the standard, and it has similar overhead to the linked example.

many m;
m.emplace<int>(3);
std::cout << m << '\n';
m.emplace<double>(3.14);
std::cout << m << '\n';

live example.

This is manual vtables, because we are basically reimplementing the vtable concept manually.