如何停止隐式转换为虚函数

How to stop implicit conversion to virtual function

本文关键字:函数 转换 何停止      更新时间:2023-10-16
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"?

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

(§10.3/2 C++11( 如果在类 Base 中声明了虚拟成员函数 vf,并且在直接或间接派生自 Base 的类 Derived 中声明了与 Base::vf 具有相同名称、参数类型列表 (8.3.5(、cv 限定符和 ref 限定符(或不存在相同(的成员函数 vf,则 Derived::vf 也是虚拟的(无论它是否如此声明(,并且它覆盖了 Base::VF。为了方便起见,我们说任何虚拟函数都会覆盖自身。

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

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;
}
  • 如果不希望派生类重写函数,则没有理由将其标记为基类virtual。将函数标记为virtual的基础是通过派生类函数覆盖来具有多态行为。

好读:
何时将C++中的函数标记为虚拟函数?

  • 如果您希望代码防止派生类中的意外覆盖。您可以使用 C++11 中的最终说明符

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

b->A::fun();

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

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

#include <iostream>
using namespace std;
struct A{
    void fun(){fun_();}
  private:
    virtual void fun_() { cout << "An"; }
};
struct B:public A{
    void fun(){cout<<"Bn";}
  private:
    virtual void fun_() final { fun(); }
};
struct C:public B{
    void fun(){cout<<"Cn";}
};
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,并提供final实现并非不合理。

我真正担心的是你隐藏A/B C::funfun()......这很麻烦,因为如果他们做不同的事情,那么你的代码可能很难推理或调试。 B决定最终确定虚拟功能意味着确定不需要这种进一步的定制。 从 A*/A&/B*/B& 工作的代码将做一件事,而无论C对象的类型是静态已知的,行为都可能不同。 模板化代码是一个可以轻松调用C::fun的地方,而模板作者或用户却没有非常清楚它。 要评估这对您来说是否是一个真正的危险,了解"乐趣"的功能目的是什么以及ABC之间的实现有何不同......

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

void fun(int ignored=0);

它将成为不参与解析虚拟呼叫的过载。请注意,即使a实际上指的是B,调用a->fun()也会调用A::fun(),所以我强烈建议不要使用这种方法,因为它会使事情变得比必要的更加混乱。

问题是:你到底想实现或避免什么?知道了这一点,这里的人们可以提出更好的方法。