如何利用GCC扩展判断重载虚方法是否被重写
GCC提供了一项扩展,可接收对象指针与虚方法指针,解析动态分派并返回自由函数指针。但当虚函数存在重载时,我不清楚该操作的最佳实现方式。以下是我的示例代码:
#include <memory> #include<iostream> using namespace std; struct B{ virtual void f(){cout<<"b\n";} virtual void f() const {}//重载导致无法直接用(F)&B::f,需要额外的(PTM)强制转换来消除歧义 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);//无重载时可直接用(F)&B::f获取baseF B b; //当`this`是B类型时,以下调用都输出b thisF(this);//当`this`是C类型时输出c thisF(&b);//当`this`是C类型时输出c baseF(this);//当`this`是C类型时输出b baseF(&b);//当`this`是C类型时输出b notBaseF(this);//当`this`是C类型时输出c notBaseF(&b);//当`this`是C类型时输出c //return thisF!=notBaseF; //错误:当this是C时结果为false return thisF!=baseF; //正确:当this是C时结果为true } }; 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(); }
我的需求是判断对象是否重写了第一个非const版本的f,因此编写了overridesf函数。核心思路是对this执行动态分派得到函数指针thisF,再将其与已知未重写f的对象的动态分派结果baseF对比。
无重载时,可直接通过(F)&B::f获取baseF;但存在重载时该表达式会有歧义,需要用(F)(PTM)&B::f(PTM强制转换用于消除重载歧义)。不过我认为(F)(PTM)&B::f并非对成员函数指针常量的直接转换,因此GCC文档中提到的直接转换规则不适用,必须借助对象(比如示例中的B())。
我有两个问题:
- 是否存在更优的方式获取
baseF?若接口包含大量纯虚函数,为构造对象获取baseF而创建一个重写所有纯虚函数但不重写f的派生类会非常繁琐。 - 由
(F)(PTM)&B::f定义的notBaseF疑似存在未定义行为,为何notBaseF(&b)的行为由this的动态类型决定?
问题解答
1. 更优的baseF获取方式
可以利用指向成员函数的指针的显式转换结合GCC扩展特性,无需构造对象。关键是先明确指定要获取的成员函数版本,再直接转换为自由函数指针:
constexpr void (B::*target_ptm)() = &B::f; // 明确选中非const版本的f using F = void (*)(B*); // 直接将成员函数指针转换为自由函数指针,GCC扩展支持这种转换(针对虚函数时,会绑定到基类的实现) auto baseF = reinterpret_cast<F>(target_ptm);
核心逻辑:当直接将基类的虚成员函数指针转换为自由函数指针时,GCC扩展会直接返回对应基类实现的函数指针,不会触发动态分派。这种方式完全不需要构造对象,即使基类有纯虚函数也能正常工作——因为我们操作的是成员函数指针本身,而非对象实例。
也可以用更简洁的写法消除重载歧义后转换:
auto baseF = (F)static_cast<void (B::*)()>(&B::f);
2. notBaseF的行为原因与未定义风险
(F)(PTM)&B::f的问题在于:它没有绑定任何对象实例,直接将成员函数指针转换为自由函数指针。对于虚函数来说,成员函数指针内部通常包含动态分派所需的信息(比如虚表偏移),没有对象的情况下,GCC扩展无法确定要使用哪个虚表——此时它可能错误复用当前函数调用上下文里this指针的动态类型信息,导致notBaseF(&b)的行为被当前this的动态类型影响。
这属于未定义行为:C++标准并未规定这种无对象绑定的虚成员函数指针到自由函数指针的转换行为,GCC扩展仅保证当你通过obj->*ptm或obj.*ptm这种绑定对象的表达式转换时,才会正确解析动态分派。直接转换未绑定的虚成员函数指针,其行为完全依赖编译器实现,可能出现不符合预期的结果。
永远不要直接转换未绑定到对象的虚成员函数指针为自由函数指针,必须通过绑定对象的表达式(比如B().*ptm)或者明确使用基类的成员函数指针直接转换(如问题1中的写法)来获取确定的函数指针。
内容的提问来源于stack exchange,提问作者Brian Moths

