【问题标题】:Is there a way to not inherit "virtualness" of a function in a subclass?有没有办法不继承子类中函数的“虚拟性”?
【发布时间】:2011-10-29 11:09:01
【问题描述】:

在 C++ 中是否有可能让一个类覆盖一个虚函数,但只有在通过超类调用该函数时才具有虚拟调度(即,当它被静态类型为子类的东西调用时)?我知道这不会发生,但是有什么办法可以接近吗?

想要这个的原因是我有两个类都公开了一个flush() 函数。在我的程序中的绝大多数时间,我直接在我知道类型的子类对象上调用flush(),所以我不需要虚拟调度。但是,我想在混合中添加一个超类,这样我就可以很少将对任一类的实例的引用传递给doSomethingThenFlush() 函数,该函数实际上会调用flush()

我知道我可以使用模板而不是虚拟函数,并且我知道我可以有两个不同的函数(例如,flushVirtual(),它刚刚调用了flushNonVirtual(),并在我不需要虚拟调度的任何地方调用flushNonVirtual()) .但这两者似乎有点像在很大程度上是句法问题上扔代码。有没有更优雅的方法来实现这一点?

也许更重要的是,有谁知道为什么虚拟性在 C++ 中被继承?

struct Base
{
  virtual ~Base(){}
  virtual void func();
};

struct Derived : public Base
{
  void func(){}
};

void callVirtually(Base &base)
{
  base.func();//this will use virtual dispatch
}

void callStatically(Derived &derived)
{
  derived.func();//I don't want/need this to use virtual dispatch
}

int main()
{
  Derived derived;
  callVirtually(derived);
  callStatically(derived);
}

【问题讨论】:

  • 万岁,C++ 半生不熟的对象模型。
  • @David 这与手头的问题有什么关系?

标签: c++ virtual


【解决方案1】:

虚拟性是继承的,因为您不知道是否有人会进一步从您的Derived 中获得。有人也可以创建一个MoreDerived,它可以传递给一个期望Derived&的函数,当他们发现这是Derived的所有虚函数的版本被bieng调用时,他们会很难过MoreDerived 的。

如果您的意思是您永远不会从 Derived 继承,因此您不想为虚函数调用付费,那么您就不走运了,因为 C++ 无法保证您会赢。永远不要从一个类继承,这是做你想做的事所必需的。

【讨论】:

  • 但在这种情况下,我决定不让Derived::func() 虚拟化(好吧,我试过了)所以我想付出代价。在我的实际项目中,Derived 永远不会被子类化,我对此很好。
  • @Karu C++ 无法保证任何类都不会从 Derived 继承,如果这就是您的意思,那么它必须这样做才能执行您希望它执行的操作。
  • 没有必要,这只是意味着期望Derived::func() 虚拟行为的人们会失望。这与func()Base 中不是虚拟的,或者Base 不存在的情况完全相同。
  • 一切正常运行是必要的。如果您将MoreDerived 传递给期望Base& 的函数怎么办?它应该调用哪个版本?它不能调用MoreDerived,因为它不是虚拟的。
  • 它应该调用MoreDerived::func(),就像我上面例子中的callVirtually()调用Derived::func()一样。我想不出任何技术上的理由会不允许这样做。
【解决方案2】:

在 C++03 中,没有。

正如其他人所说,这是一种编译器优化(也是一种经常使用的优化),只要它可以评估对象的运行时类型,就可以对他的调用进行去虚拟化。

但是,在 C++0x 中,我们得到了两个新关键字:overridefinal,它们都可以应用于成员函数(final 也可以应用于类)。

  • override:指定此函数覆盖基类中的虚函数,在不是这种情况时收到警告很有用
  • final:指定这个函数(虚函数)不能在子类中被覆盖。

因此,您的班级将变成:

struct Derived : public Base
{
  void func() final {}
};

注意:使用 final 并不强制编译器去虚拟化函数调用(从标准的角度来看),但任何值得其盐的编译器都应该这样做。

【讨论】:

    【解决方案3】:

    在您的具体示例中,如果 callStatically 被内联,编译器可能会避免虚拟函数调度,因为它可以看到对象的实际类型(因为它是一个局部变量)。

    也许你的编译器也可以避免像这样的情况下的虚拟调度:

    class Foo {
    public:
        callStatically() { d.func() }
    private:
        Derived d;
    };
    

    无论callStatically 是否内联,编译器都可能执行此优化,因为它可以看到变量成员的实际类型。

    但据我所知,一般来说没有办法强制编译器绕过虚拟调用。

    【讨论】:

      【解决方案4】:

      答案就在你的问题中。

      derived.func();  // no virtual dispatch
      

      当您使用对象调用virtual 函数时,没有virtual 调度。它使用对象的静态类型调用函数。

      virtual 函数仅在您尝试使用 指针引用 调用函数时出现。

      编辑:在您更新的问题中,您使用Derived& 调用func()。通过引用调用将确保发生virtual 调度。所以没有语言工具(如Java中的final),它将停止virtual调度。

      【讨论】:

      • 对不起,这个例子不正确 :) 我已经更新了它以匹配我真正想问的内容。
      • 编译器优化应该停止virtual调度,尤其是在函数调用被内联时。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-10-12
      • 2016-03-04
      • 2013-01-27
      • 1970-01-01
      • 2020-12-20
      • 2010-09-28
      • 2011-11-17
      相关资源
      最近更新 更多