一旦函数名称被声明为“虚拟”,它是否可以再次成为非虚拟的?

时间:2011-07-28 21:57:37

标签: c++ virtual

所以,让我们说我有一些类链,其中每个类都是从它之前的类派生出来的。无论出于何种原因,他们都喜欢为某些成员函数使用相同的名称。有点像这样:

class C1 { public:             void f() { cout<<"C1"; }; };
class C2 : public C1 { public: void f() { cout<<"C2"; }; };
class C3 : public C2 { public: void f() { cout<<"C3"; }; };

显然,如果我只声明一些对象,然后从它们调用函数f,所有将调用与它们各自的对象类型相关联的函数:

C1 c1; c1.f(); // prints C1
C2 c2; c2.f(); // prints C2
C3 c3; c3.f(); // prints C3

现在,如果我声明一些指向对象的指针,然后从它们调用函数f,所有都将调用与它们各自的指针类型相关联的函数:

C1* p1 = &c1; p1->f(); // prints C1
C1* p2 = &c2; p2->f(); // prints C1
C1* p3 = &c3; p3->f(); // prints C1
C2* p4 = &c2; p4->f(); // prints C2
C2* p5 = &c3; p5->f(); // prints C2
C3* p6 = &c3; p6->f(); // prints C3

所有这些都是超级的。我要么调用与对象类型相关联的函数,要么调用与指针类型相关联的函数...

当然我可以将功能设为'虚拟'。然后,如果我从某个对象调用该函数,我将不会改变行为;但是,如果我从某个指针调用该函数,那么我不会只调用指针类型的函数,我实际上会调用指针所指向的对象类型的函数。到目前为止一切都很好。

我甚至可以通过继承链中途进行虚拟更改。假设我在类C2中的函数f之前放置了一个虚拟。现在该函数已经变为虚拟(即,当从指针调用时,它使用对象指向的类型而不是指针类型来解析函数调用),不仅对于它自己的类,而且对于所有将来的类,来自它。

我的问题是:一旦一个函数被声明为虚拟(在继承链中的某个点),它是否可以恢复为非虚拟(继承链的下方)?

为了澄清:当我说恢复到非虚拟行为时,我的意思是当我从指针调用函数时,它将使用指针的类型来解析函数调用(而不是指针的对象类型)指着)。

7 个答案:

答案 0 :(得分:2)

简单回答:不。

如果您知道虚拟函数在C ++中的工作方式,您就会知道它是不可能的。简单来说,每个类都有一个表,其中包含虚函数列表和被覆盖函数的地址。当你用virtual覆盖一个函数时,该函数将被列在它所声明的类的表中的那个列表中,所以它将存在于继承链的其余部分。

答案 1 :(得分:2)

  

一旦一个函数被声明为虚拟(在继承链中的某个点),它是否可以恢复为非虚拟(继承链的下游)?

没有。一旦在继承行的某个地方创建了一个函数签名virtual,它就会保持这种状态:派生类中具有相同签名的每个函数也都是virtual

解决此问题的一种方法是使用(滥用?)template method pattern

struct Base {
    virtual void doFoo() { bar(); }
    void foo() { doFoo(); }
};

struct Derived1 : public Base {
    virtual void doFoo() { baz(); }  // "overrides" foo via doFoo
};

struct Derived2 : public Base {
    void foo() { quux(); }  // "un-virtualize" foo by decoupling it from doFoo
};

在这个继承树中,指针类型将决定调用哪个foo;如果它是来自Base的那个,则指向对象的类型将决定调用哪个doFoo

答案 2 :(得分:1)

简单回答:不。

一旦虚拟化,它将在所有派生类中都是虚拟的(即使以后不使用虚拟关键字)。

由于这个原因,一些较新的语言强迫您使用另一个关键字,以便您知道您正在覆盖虚拟方法(因为它可能不是很明显),但是没有一个允许您取消虚拟方法。

答案 3 :(得分:1)

正如其他人已经说过的那样,你不能对一个函数进行去虚拟化,但是没有什么可以阻止你调用你需要的函数的特定版本,只要它对于底层类是合法的:

C1* p1 = &c1; p1->C1::f(); // prints C1
C1* p2 = &c2; p2->C1::f(); // prints C1, not C2
C1* p3 = &c3; p3->C1::f(); // prints C1, not C3
C2* p4 = &c2; p4->C2::f(); // prints C2
C2* p5 = &c3; p5->C2::f(); // prints C2, not C3
C3* p6 = &c3; p6->C3::f(); // prints C3

答案 4 :(得分:0)

没有

使用模板方法解决此问题:

class C1 { public:  virtual void f() { g(); }; void g() { std::cout<<"C1"; }; };
class C2 : public C1 { public: virtual void f() { std::cout<<"C2"; }; };
class C3 : public C2 { public: virtual void f() { g(); }};

现在C3和它的后代有C1的行为。

答案 5 :(得分:0)

不确定,它会回答你 - 你可以在VC ++中使用novtable属性。

答案 6 :(得分:-2)

不,它无法恢复。我从来不需要这个,也无法想象需要这个类层次结构的真实例子。

另外,建议避免深层次,例如作者:B.Straustrup。