获取没有对象的类的vtable

Posted

技术标签:

【中文标题】获取没有对象的类的vtable【英文标题】:Obtain the vtable of a class without an object 【发布时间】:2016-03-04 16:48:49 【问题描述】:

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

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

void setType( const DataType& newType )

    memcpy( this, &newType, sizeof(DataType) );

但是,我对这种方法有一个问题:我没有目标类的对象来复制 vtable,并且不想创建一个对象,因为某些类型的构建成本很高。

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

如果它具有一定的可移植性会更好,但我基本上已经接受了编译器特定的;因此,如果没有其他选择,只能接受 GCC/G++ 方法。我们还假设我只关心在相当标准的操作系统和架构上构建它。

我正在使用 C++11,如果这有助于解决这个问题。

编辑:我想完全清楚,我知道这种行为有多危险。尽管我的介绍可能会建议,但我对这个想法以及它在非常受控的环境中的狭窄应用更感兴趣,而不是我对它作为生产软件的好想法。

【问题讨论】:

当然你需要一个对象的实例,那么为什么不在它被创建之后修改它呢? [虽然修改 vtables 似乎是一个糟糕的主意,它甚至可能不存储在可写内存中,而且现在的编译器确实会跟踪对象并跳过 vtable 访问,如果它“知道”要调用什么] 如果你不想在运行时改变对象实例的行为,而是实现State Design Pattern。你想要的东西不可能以可移植的方式实现,编译器不需要根据 c++ 标准使用 vtable 来实现动态多态性。 两个想法:(1) PIMPL,您可以在其中创建一个没有 impl 东西的对象来获取 vtable(为此使用一些特殊的 ctor)。这将使为 vtable 创建一个“虚拟”对象变得轻量,而不是实际使用。 (2) 实现一个具有局部静态变量的模板化工厂函数,持有一个虚拟实例(一种单例)只是为了获取 vtable。所以你总共只有一个类型的对象作为你的开销,我想这是可以的。 (1+2) 甚至可以组合,但我想没必要。 @MatsPetersson 我知道这是建立在非标准行为之上的,因此我指出这是对 vtables 的“滥用”。我正在尝试评估,除了我提出的这个问题之外,它是否是我感兴趣的平台上的一个可行概念。非常可能的问题并没有真正影响我的问题。 - 至于必须有一个实例,不,我没有:例如,我有一个子类来包装一个整数,但我从不创建该子类的实例。我只是在将整数放入包装器时放置它的 vtable。实际上,每次设置时,我都会将基类转变为继承类。 在链接代码中,对象只不过是它的 vtable。其次,考虑安置新的和歧视性的工会。 【参考方案1】:
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()constreturn 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;
  
;

这是一个手动的 vtable 设计。它可以存储最多 100 个字节的对齐方式小于可以打印到流的 double 的任何内容。

“擦除”到更多(或不同)操作很容易。例如,能够复制/移动到另一个许多。

它不违反标准,并且与链接示例具有相似的开销。

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

live example.

这是手动 vtable,因为我们基本上是手动重新实现 vtable 概念。

【讨论】:

虽然不是对我提出的问题的直接回答,但这对我陈述的用例来说似乎比我正在调查的更有用。因此,我会接受它作为答案。感谢您编写示例。

以上是关于获取没有对象的类的vtable的主要内容,如果未能解决你的问题,请参考以下文章

(反射)类的加载(33)

获取 Qt 对象的大小

java反射--获取成员变量信息

反射

Java-反射机制

将图像分配给从 Qimage 继承的类的对象