我一直在寻找一种自定义多态解决方案来改善二进制兼容性。问题是指针成员在不同平台上的大小各不相同,因此即使是“静态”宽度成员也会被迫推动生成二进制不兼容的布局。
我理解,大多数编译器都以类似的方式实现v-table:
__________________
| | | |
|0| vtable* | data | -> ->
|_|_________|______|
E.g。将v-table作为对象的第一个元素,并且在多重继承的情况下,继承的类按顺序与适当的填充对齐以进行对齐。
所以,我的想法如下,将所有v-tables(以及所有不同的“平台宽度”成员)“置于”后面:
__________________
| | | |
<- | vtable* |0| data | ->
|_________|_|______|
这样,0的布局右侧(第一个数据成员的对齐边界)仅包含具有显式和可移植大小和对齐的类型(因此它可以保持一致),同时您仍然可以移植导航通过v-tables和其他指针成员使用带有平台宽度stride的索引。此外,由于左侧的所有成员都具有相同的大小和对齐方式,因此可以减少布局中额外填充的需要。
当然,这意味着“this
”指针将不再指向对象的开头,而是一些偏移量,它将随着每个类而变化。这意味着“new
”和“delete
”必须进行调整才能使整个方案发挥作用。这会产生可衡量的负面影响,考虑到这种或那种方式,无论如何都会在访问成员时进行抵消计算吗?
我的问题是,具有更多经验的人是否可以指出使用此方法的潜在警告。
编辑:
我做了一个快速测试,以确定额外的偏移计算是否会对虚拟调用的性能产生不利影响(是的,我知道它是C问题中的C ++代码,但我没有纳秒分辨率计时器C,加上整点,以便与现有的多态实现进行比较):
class A;
typedef void (*foo)(A*);
void bar(A*) {}
class A {
public:
A() : a(&bar) { }
foo a;
virtual void b() {}
};
int main() {
QVector<A*> c;
int r = 60000000;
QElapsedTimer t;
for (int i = 0; i < r; ++i) c.append(new A);
cout << "allocated " << c.size() << " object in " << (quint64)t.elapsed() << endl;
for (int count = 0; count < 5; ++count) {
t.restart();
for (int i = 0; i < r; ++i) {
A * ap = c[i]; // note that c[i]->a(c[i]) would
ap->a(ap); // actually result in a performance hit
}
cout << t.elapsed() << endl;
t.restart();
for (int i = 0; i < r; ++i) {
c[i]->b();
}
cout << t.elapsed() << endl;
}
}
在测试了6000万个对象(7000万个无法在我正在使用的32位编译器上进行分配)之后,看起来调用常规虚函数和通过不是指针调用的指针之间没有任何可测量的差异。对象中的第一个元素(因此需要额外的偏移量计算),即使在函数指针的情况下,内存地址也会传递两次,例如传递以查找a
的偏移量,然后传递到a
)。在发布模式下,两个函数的时间相同(对于60mil调用,+ / - 1 nsec),在调试模式下,函数指针实际上一致性快1%(可能函数指针比虚函数需要更少的资源)。
分配和删除时调整指针的开销似乎实际上可以忽略不计,完全在误差范围内。这是预期的,考虑到它应该只添加一个已经在具有立即值的寄存器上的值的单个增量,这应该在我打算定位的平台上进行一个循环。
答案 0 :(得分:0)
仅供参考,vtable的地址位于对象的第一个DWORD / QWORD,而不是表本身。 vtable在同一个类/结构的对象之间共享。
平台之间具有不同的vtable大小是一个无问题的BTW。不兼容的平台无法执行其他平台的本机代码,并且要使二进制转换工作,模拟器需要知道原始架构。
您的解决方案的主要缺点是性能和当前实现的复杂性。