关于c ++:如何停止隐式转换为虚函数

How to stop implicit conversion to virtual function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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中,非virtualfun()运行虚拟fun_()以便在B中定制行为,但是在派生类上调用fun()的任何人都只能看到非多态版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
using namespace std;

struct A{
    void fun(){fun_();}
  private:
    virtual void fun_() { cout <<"A
"
; }
};

struct B:public A{
    void fun(){cout<<"B
"
;}
  private:
    virtual void fun_() final { fun(); }
};

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
    c.fun();    // notice that this outputs"C" which I think is what you want
}

如果使用C++ 03,你可以简单地省掉"最终"关键字——它只是为了防止EDOCX1和11个派生类(如EDCOX1×19)中的虚拟行为的进一步不必要的重写。

(您可能会发现将此与"非虚拟接口模式"进行对比是有趣的——参见萨特和Alexandrescu的C++编码标准,第39点)

讨论

拥有funvirtual的A意味着在派生类中覆盖它是派生类的一种必要的自定义能力,但是在派生层次结构中的某个点上,实现行为的选择可能缩小到1,并提供final实现并不不合理。

我真正担心的是你把A/Bfun()C::fun藏起来了……这很麻烦,好像他们做了不同的事情,那么您的代码可能很难解释或调试。B决定最终确定虚拟功能意味着不需要进一步定制。从A*/A&/B*/B&工作的代码只会做一件事,而当C对象的类型静态已知时,行为可能会有所不同。模板化代码是一个很容易调用C::fun的地方,而模板作者或用户对此并不十分了解。为了评估这是否对您来说是真正的危险,了解"乐趣"的功能目的以及ABC之间的实施可能有什么不同将有助于了解。


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

(§10.3/2 C++11) If a virtual member function vf is declared in a class Base and in a class Derived, derived directly or indirectly from Base, a member function vf with the same name, parameter-type-list (8.3.5), cv-qualification, and ref-qualifier (or absence of same) as Base::vf is declared, then Derived::vf is also virtual (whether or not it is so declared) and it overrides Base::vf. For convenience we say that any virtual function overrides itself.

但是,如果您希望使用对应于静态指针(而不是动态类型)的函数(即,在您的示例中,EDCOX1×4)代替EDCOX1(5),假定指针被声明为EDCOX1(6)),那么您至少可以在C++ 11中使用下面的别名定义来访问静态(=编译时)类型:

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

这是您在main()或其他任何地方使用的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}


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

1
b->A::fun();

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


  • 如果不希望派生类重写函数,那么就没有理由在基类中标记它virtual。标记一个函数virtual的基础是通过派生类函数overidding来产生多态行为。

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

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


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

1
void fun(int ignored=0);

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

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