基类能否知道派生类是否已重写虚拟方法

Can a base class know if a derived class has overridden a virtual method?

本文关键字:是否 重写 虚拟 方法 派生 基类      更新时间:2023-10-16

同样的问题也存在于C#上,但不适用于C++。

class Base
{
void dispatch()
{
if (newStyleHasBeenOverridden())   //how to find this out?
newStyle(42);
else
oldStyle(1, 2);
}
virtual void oldStyle(int, int) { throw "Implement me!"; }
virtual void newStyle(int) { throw "Implement me!"; }
}
class Derived:public Base
{
void newStyle(int) override
{
std::cout<<"Success!";
}
}

警告:此解决方案不是跨平台的,因为它依赖于GCC扩展和一些未定义的行为。

GCC 允许语法通过说this->*&ClassName::functionName来从this的 vtable 中获取指向函数的指针。实际使用它可能不是一个好主意,但无论如何,这里有一个演示:

#include <iostream>
class Base {
public:
void foo() {
auto base_bar_addr = reinterpret_cast<void*>(&Base::bar);
auto this_bar_addr = reinterpret_cast<void*>(this->*&Base::bar);
std::cout << (base_bar_addr == this_bar_addr ? "not overridden" : "overridden") << std::endl;
}
virtual void bar() { };
};
class Regular : public Base { };
class Overriding : public Base {
public:
virtual void bar() { };
};
int main() {
Regular r;
r.foo();
Overriding o;
o.foo();
}

对于后代:

  • ICC允许语法,但它有不同的含义,这与只说&Base::bar相同,所以你总是认为它没有被覆盖。
  • Clang和MSVC完全拒绝了代码。

这是一个设计问题。

但是,为了回答实际问题,有几种方法可以在不重新设计的情况下完成此操作(但实际上,您应该重新设计它)。

一个(可怕的)选择是调用 newstyle 方法并捕获未重写时发生的异常。

void dispatch() {
try {
newStyle(42);
} catch (const char *) {
oldStyle(1, 2);
}
}

如果 newStyle 已被重写,则将调用该重写。 否则,基本实现将抛出,调度将捕获,然后回退到oldStyle。 这是对异常的滥用,性能会很差。

另一种(稍微不那么可怕)的方法是使newStyle的基本实现向前发展到oldStyle。

void dispatch() {
newStyle(42);
}
virtual void newStyle(int) { oldStyle(1, 2); }
virtual void oldStyle(int, int) { throw "implement me"; }

这至少朝着更好的设计方向发展。 继承的要点是允许高级代码能够互换使用对象,而不管其专用性如何。 如果调度必须检查实际对象类型,那么您违反了 Liskov 替换原则。 调度应该能够以相同的方式处理所有对象,并且行为的任何差异都应该来自重写方法本身(而不是重写的存在)。

更简单的是,调度决策由Derived类完成。 摘要Base类基本上只是一个"接口",Derived应该在其中实现所有virtual函数。

这个问题听起来也像XY问题。

我以为这就是你想要的:

class Base // abstract class
{
virtual void oldStyle(int, int) = 0; // pure virtual functions
virtual void newStyle(int) = 0;      // needs to be implemented
};
class Derived:public Base
{
public:
Derived(bool useNewStyle): _useNewStyle(useNewStyle) {}
void newStyle(int) { std::cout << "new style"; }
void oldStyle(int, int) { std::cout << "old style"; }
void dispatch()
{
if (_useNewStyle) {
newStyle(42);
return;
}
oldStyle(1, 2);
return;
}
private:
bool _useNewStyle = false;
};
Derived d(true); // use new style
d.dispatch();    // "new style"