【问题标题】:How to stop implicit conversion to virtual function如何停止隐式转换为虚函数
【发布时间】:2013-07-02 05:35:06
【问题描述】:
struct A{
    virtual void fun(){cout<<"A";}
};
struct B:public A{
    void fun(){cout<<"B";}
};
struct C:public B{
    void fun(){cout<<"C";}
};
int main() 
{
    C c;B b1;   
    A *a=&b1;
    a->fun(); //1
    B *b=&c;
    b->fun(); //2
    return 0;
}

在上面的代码中,B::fun() 被隐式转换为虚函数,因为我已将 A::fun() 设为虚拟。我可以停止这种转换吗?

如果不可能的话,有什么替代方法可以让上面的代码打印“BB”?

【问题讨论】:

  • 如果你不想要一个虚函数……你为什么一开始就在基类中把它变成虚函数?
  • 我希望 A::fun() 是虚拟的
  • @banarun 如果你不想让它像一个虚函数一样,为什么?

标签: c++ oop virtual-functions


【解决方案1】:

虚函数在所有派生类中都是虚函数。没有办法阻止这种情况。

(§10.3/2 C++11) 如果虚成员函数 vf 在类 Base 和 Derived 类中声明,直接或间接从 Base 派生,则具有相同名称、参数类型的成员函数 vf -list (8.3.5)、cv-qualification 和 ref-qualifier(或不存在相同)作为 Base::vf 被声明,然后 Derived::vf 也是虚拟的(无论是否如此声明)并且它覆盖基地::vf。为方便起见,我们说任何虚函数都会覆盖自身。

但是,如果您想使用对应于 static 的函数,而不是 dynamic,则指针类型(即,在您的示例中, B::fun 而不是 C::fun,假设指针被声明为 B*),那么至少在 C++11 中,您可以使用下面的别名定义来访问静态 (=compile-time)类型:

template <typename Ptr>
using static_type = typename std::remove_pointer<Ptr>::type;

这就是您在main()(或其他任何地方)中使用它的方式:

int main() 
{
  C c; B b1;   

  A *a = &b1;
  a->fun();

  B *b = &c;

  /* This will output 'B': */    
  b->static_type<decltype(b)>::fun();

  return 0;
}

【讨论】:

    【解决方案2】:
    • 如果您不希望您的派生类覆盖该函数,那么您没有理由在基类中标记它virtual。标记函数virtual 的基础是通过派生类函数覆盖具有多态行为。

    好读:
    When to mark a function in C++ as a virtual?

    • 如果您希望您的代码防止在派生类中意外覆盖。您可以在 C++11 中使用 final specifier

    【讨论】:

    • 不,我实际上希望基类是虚拟的,但我不希望派生类是虚拟的。
    • 这不是阻止覆盖基类中的函数,而是允许您使用非virtual 函数覆盖?
    • @banarun 这完全违背了目的,如果只有基类是“虚拟的”那么它就不是虚拟的。
    • @banarun:如果您在派生类中没有覆盖行为,那么为什么要将基类函数标记为virtual?只有在派生类中有覆盖行为时才有意义。
    • 我希望基类被派生类覆盖。但我不希望派生类被派生自它的另一个类覆盖。
    【解决方案3】:

    是的,如果您想显式调用特定类中的函数,您可以使用完全限定名称。

    b->A::fun();
    

    这将调用属于Afun()的版本。

    【讨论】:

      【解决方案4】:

      以下实现了您要求的可观察行为。在A 中,非virtual fun() 运行虚拟fun_(),因此可以在B 中自定义行为,但任何在派生类上调用fun() 的人都只会看到非多态版本。

      #include <iostream>
      using namespace std;
      
      struct A{
          void fun(){fun_();}
        private:
          virtual void fun_() { cout << "A\n"; }
      };
      
      struct B:public A{
          void fun(){cout<<"B\n";}
        private:
          virtual void fun_() final { fun(); }
      };
      
      struct C:public B{
          void fun(){cout<<"C\n";}
      };
      
      int main()
      {
          C c;B b1;
          A *a=&b1;
          a->fun(); //1
          B *b=&c;
          b->fun(); //2
          c.fun();    // notice that this outputs "C" which I think is what you want
      }
      

      如果使用 C++03,您可以简单地省略“final”关键字 - 它只是为了防止对 B 派生类(例如 C)中的虚拟行为的进一步不必要的覆盖。

      (您可能会发现将此与“非虚拟接口模式”进行对比会很有趣 - 请参阅 Sutter 和 Alexandrescu 的 C++ 编码标准,第 39 点)

      讨论

      A 具有 fun 虚拟意味着在派生类中覆盖它是派生类的必要定制能力,但在派生层次结构中的某个点,实现行为的选择可能会缩小到 1 并提供 @ 987654332@ 实现不无道理。

      我真正担心的是你 隐藏 A/Bfun()C::fun... 这很麻烦,好像他们做不同的事情然后你的代码可能会非常难以推理或调试。 B 最终确定虚拟功能的决定意味着确定不需要这种进一步的定制。从A*/A&amp;/B*/B&amp; 工作的代码将做一件事,而无论C 对象的类型是静态已知的,行为可能会有所不同。模板代码是一个可以很容易地调用C::fun 的地方,而模板作者或用户并不十分清楚这一点。要评估这对您来说是否是真正的危险,了解“乐趣”的功能目的是什么以及ABC 之间的实现可能有何不同会有所帮助...。

      【讨论】:

        【解决方案5】:

        如果你像这样在 B 中声明函数

        void fun(int ignored=0);
        

        它将成为一个重载,不会参与解决虚拟调用。请注意,即使a 实际上指的是B,调用a-&gt;fun() 也会调用A::fun(),因此我强烈建议不要使用这种方法,因为它会使事情变得比必要的更加混乱。

        问题是:您想要实现或避免的究竟是什么?知道这一点后,这里的人们可以提出更好的方法。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-09-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多