【问题标题】:Can a base class know if a derived class has overridden a virtual method?基类能否知道派生类是否覆盖了虚方法?
【发布时间】:2018-05-17 00:11:29
【问题描述】:

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!";
    }
}

【问题讨论】:

  • 简短的回答是:不。 C++ 不能以这种方式工作。你需要弄清楚你真正的问题是什么,因为this is an XY problem
  • 也许你可以比较两个类成员函数的指针(地址)。但正如@SamVarshavchik 所说,你为什么需要这样做?
  • 不,@DavidRTribble——你不能“比较两个类成员函数的指针”。对于初学者来说,它们是两个不同的类别。最后,您会发现指向虚拟类成员函数的指针是同一个指针,无论特定实例的虚拟类是否被覆盖。毕竟虚函数就是这样工作的。
  • @SamVarshavchik,是的,我在发布后几分钟就意识到了这一点。但就像你说的,OP 的真正问题是正确使用覆盖的成员函数。

标签: c++ c++11


【解决方案1】:

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

GCC 允许语法通过说this-&gt;*&amp;ClassName::functionNamethis 的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 允许语法,但它有不同的含义,和刚才说的 &amp;Base::bar 一样,所以你会一直认为它没有被覆盖。
  • Clang 和 MSVC 直接拒绝代码。

【讨论】:

  • 尽管有您的警告,这可能会导致人们误入歧途,但对于一个离题的问题来说,这是一个很好的答案。 +1
  • 标准中关于指针比较的说法是:否则,如果其中一个是指向虚成员函数的指针,则结果未指定。
  • 唉,这是一个跨平台库,所以 GCC 特定的解决方案不能完全回答它。
  • 我在顶部添加了一个更大的警告,试图不让太多人走上未定义行为的黑暗道路。
【解决方案2】:

这是一个设计问题。

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

一个(可怕的)选择是调用 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"; }

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

【讨论】:

  • > 一个(可怕的)选项是调用 newstyle 方法并捕获未覆盖时发生的异常。这是我的第一个解决方案,我试图通过查找方法是否重载来解决这个问题!
  • > newStyle forward to oldStyle 这不能正常工作,因为旧样式可以访问更多信息,即附加的第二个参数。
  • 是的,我说这是一个糟糕的解决方案。您的示例对参数进行了硬编码;我只是跟随你的领导。您可以将额外信息分派存储在成员变量中,newStyle 的基本实现将访问这些变量以将值传递给 oldStyle。然后你会遇到多线程问题......这是一团糟。
  • newStyleoldStyle 作为线程函数调用,oldStyle 有 threadId 参数,newStyle 没有它:)
【解决方案3】:

为了简单起见,调度决定由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"

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-06-01
    • 1970-01-01
    • 2013-01-28
    • 2019-08-31
    • 2011-09-11
    • 2020-10-21
    • 2021-06-17
    • 2018-12-17
    相关资源
    最近更新 更多