这里解释了 gcc 提供了一个扩展,它接受一个指向对象的指针和一个指向虚拟方法的指针,并解决了动态分派的问题,给出了一个自由函数指针。我不清楚当虚拟函数重载时如何最好地做到这一点。我下面有一些示例代码。
科利鲁
#include <memory>
#include<iostream>
using namespace std;
struct B{
virtual void f(){cout<<"b\n";}
virtual void f() const {}//this makes things more interesting, can no longer use (F)&B::f, would need extra cast (F)(PTM)&B::f
bool overridesf(){
constexpr void (B::*ptm)()=&B::f;
using PTM = decltype(ptm);
using F = void (*)(B*);
auto thisF = (F)(this->*ptm);
auto notBaseF = (F)(PTM)&B::f;
auto baseF = (F)(B().*ptm);//can simply use (F)&B::f when no overload is present
B b;
//below, all print b when `this` is a B
thisF(this);//c when `this` is a C
thisF(&b);//c when `this` is a C
baseF(this);//b when `this` is a C
baseF(&b);//b when `this` is a C
notBaseF(this);//c when `this` is a C
notBaseF(&b);//c when `this` is a C
//return thisF!=notBaseF; //wrong: is false when this is a c
return thisF!=baseF; //works correctly: true when this is a c
}
};
struct C:B{
void f()override{cout<<"c\n";}
};
int main() {
auto b=make_unique<B>();
cout<<b->overridesf()<<endl;
b=make_unique<C>();
cout<<b->overridesf();
}
我有兴趣查看一个对象是否覆盖了第一个非常量
f
,并且我创建了一个函数
overridesf
来执行此操作。这个想法是在 this
上进行动态调度以获取函数指针 (thisF
) 并将此函数指针与我们对已知不会覆盖 f 的对象进行动态调度时得到的结果进行比较 (baseF
).事实上,如果f
没有被重载,那么我们可以简单地通过表达式
baseF
得到(F)&B::f
函数指针。然而,如果 f
被重载,这个表达式就会变得不明确,我们将不得不执行 (F)(PTM)&B::f
,其中 PTM 强制转换解决了重载的歧义。但是(我认为)表达式 (F)(PTM)&B::f
不是 PMF 常量的直接转换(因为中间转换),因此链接页面末尾有关直接转换的部分不适用,您需要一个对象(在我的例子中B()
)。我有两个问题。首先有没有更好的方法来获得baseF
?如果接口有许多纯虚函数,那么创建一个派生类来重写所有纯虚函数(但当然不是
f
)以构造一个对象以获得baseF
会很烦人。另一个问题是 notBaseF
是怎么回事,它是由表达式
(F)(PTM)&B::f
定义的。据推测这是未定义的行为,但是有没有解释为什么 notBaseF(&b)
的行为是由 this
的动态类型决定的?好的,据我了解,您希望节省访问 vtable(“动态调度”)的时间 - 以防函数未被覆盖。为了做到这一点,您将应用像
overridesf
中那样的动态方法 - 这将需要更多时间。
回答没有任何方法(判断函数是否被覆盖)比 vtable 访问更快。我没有证据,但原因如下。如果这是正确的,那么这样的优化是不可能的。
为什么?vtable 访问需要访问一个以上的间接地址 - 可能只是一个 CPU 周期。判断函数是否被覆盖的动态方法至少需要访问两个地址和一个比较。
一条出路首先确定动态调度需要多少时间以及函数本身需要多少时间(净时间)以及优化是否值得。如果开销只有 10%,那么如果找到静态访问方法,您将获得 10% 的收益。
静态调度如果静态调度的优化确实值得,那么不要使用动态方法,而应使用静态方法。利用调用者的知识,他们可能知道必须调用哪个函数。模板可以帮助实现这一目标。