c++编译器中的pod和VPtr设计

PODs and VPtr Design in C++ Compilers

本文关键字:VPtr 设计 pod 编译器 c++      更新时间:2023-10-16

这个问题更多的是关于语言设计,而不是关于改变c++的约定。

在考虑Go编程语言(它如何将数据从接口中分离出来,有效地将所有对象转换为结构体)和c++中的pod(我喜欢在分配大量小型结构体类对象时使用memset/memcpy)时,我想知道 c++编译器将vptr直接附加到对象的实例的惯例,这会弄乱布局。

这是一个要求还是一个惯例?

如果你正在设计一个替代编译器,你能有一个大的外部查找表来代替vptr吗?例如, map<void*,vptr> ?就内存设置而言,所有实例都是POD,要查找它的vptr,我们将获取它的地址并查看大型外部查找表。

缺点是,所有内容都需要查找。这是一个可行的替代设计还是有严重的缺点?

性能很可能会受到影响。有些语言为方法查找添加了某种缓存。

对于给定的选择器(或方法名),您可以使用不同的方法来查找要调用的方法。看看Smalltalk, Self, Javascript, Common Lisp Object system或者Ocaml

然而,一些语言的实现包含巧妙的技巧(缓存,JIT,…),使它们与c++一样快(但这是实现问题,而不是语言设计问题)。

该映射必须存储那些void*指针,这些指针可能与vptr大小相同,因此实际上使用了更多的内存,因为vptr s也必须存储。map查找不是简单的——如果有碰撞,你必须执行线性搜索,直到找到完全匹配。因此,查找平均速度会变慢,并且会使用更多的内存。

实际上,在对象前加上vptr并不会打乱布局——子对象的布局与没有vptr时完全一样。

c++并不一定要有v-table,尽管这是所有主流编译器都采用的路线。

除非有虚方法,否则不会得到虚表指针。如果你不想要,那就不要使用virtual。如果只需要POD,则使用c。

如果你正在设计一个可替代的编译器,你能有一个大的外部查找表来代替vptr吗?例如,像map?就内存设置而言,所有实例都是POD,要查找它的vptr,我们将获取它的地址并查看大型外部查找表。

这解决了什么?memset仍然不能正常工作-它通过绕过复制方法来破坏引用。它也不能解决动态调度问题,因为memset不会将类型注册到映射中。

所有实例都是POD

这里有一个严重的误解。

具有虚方法的类和非pod的类之间有一个非常重要的区别:复制构造函数的存在是有原因的。

简单示例(简化):

template <typename T>
class unique_ptr {
public:
  unique_ptr(): _ptr(nullptr) {}
  explicit unique_ptr(T* p): _ptr(p) {}
  unique_ptr(unique_ptr const&) = delete;
  unique_ptr(unique_ptr&& other): _ptr(other._ptr) { other._ptr = 0; }
  unique_ptr& operator=(unique_ptr other) {
    std::swap(_ptr, other._ptr);
    return *this;
  }
  ~unique_ptr() { delete _ptr; }
  T& operator*() const { return *_ptr; }
  T* operator->() const { return _ptr; }
private:
  T* _ptr;
}; // class unique_ptr<T>

这个类在布局上是一个POD。然而,memcpy将公然违反使用条款并导致未定义的行为:复制构造函数被删除是有原因的!

然而,没有虚拟方法。

任何直接或间接拥有内存的类都是非pod。考虑到实际使用中嵌入std::string的类的数量……这是一个相当常见的场景。

Go呢?

现在,这并不意味着类应该用虚方法(以及虚指针)来构建,但是这样做肯定更简单。我个人是Shims的粉丝,但是存在内存所有权/对象生命周期问题。