C++ 为什么虚拟关键词是;持久的;在C++;?

C++ 为什么虚拟关键词是;持久的;在C++;?,c++,inheritance,C++,Inheritance,假设我有一个函数print(),分为3个类a、B和C。 C继承自B继承自A。 关键字virtual仅用于A 为什么下面两个都在C中使用print() 这里的直觉是什么 如果您要编译/运行以下完整工作代码: #include <iostream> #include <cstdlib> using namespace std; class A{ public: A(){ cout << "construct A" << en

假设我有一个函数print(),分为3个类a、B和C。 C继承自B继承自A。 关键字virtual仅用于A

为什么下面两个都在C中使用print()

这里的直觉是什么

如果您要编译/运行以下完整工作代码:

#include <iostream>
#include <cstdlib>

using namespace std;

class A{
public:
    A(){
        cout << "construct A" << endl;
    }
    virtual void print(){
        cout << "A says" << endl;
    }
};


class B: public A{
public:    
    B(){
        cout << "construct B" << endl;
    }
    void print(){
        cout << "B says" << endl;
    }    
};

class C: public B{
public:    
    C(){
        cout << "construct C" << endl;
    }
    void print(){
        cout << "C says" << endl;
    }    
    void print(int x){
        cout << "C says " << x << endl;
    }
};


int main(){

    A* ac = new C();
    ac->print();

    B* bc = new C();
    bc->print();

    return 0;
}
#包括
#包括
使用名称空间std;
甲级{
公众:
(){

CUT< P>因为C++是这样工作的——一旦一个特定签名的函数被标记为“代码>虚拟< /代码>,无论每个派生类是否仍然使用关键字,都仍然是代码>虚拟<代码>。我的首选是总是使用额外的(冗余)。
virtual
来明确发生了什么,但其他人认为不需要它,所以您不妨将其忽略

(但是,具有不同签名的重载不会自动是虚拟的,实际上会隐藏同名的基类方法,除非使用
using
指令将基类定义引入派生范围。)


至于原因,我认为没有任何特定的语言理由禁止您“去虚拟化”一个函数。我猜Stroustrup在早期决定原谅人们在派生类中忘记了
virtual
,并因此导致意外行为。

该语言可以以任何一种方式工作-这是Stroustrup做出的选择:

  • 实际上,在不同的派生深度上重写select函数很容易,而无需在每个中间类中重新列出所有未重写的函数。随着代码的发展,这减少了维护负担,当您查看派生类时,它强调的是实际更改或添加的内容,而不太混乱

  • 如果它像您期望的那样工作,并且您必须在
    B
    中再次显式声明虚拟函数,那么编译器将期望实现,并且链接器将失败,因此需要一些新的符号来指示您只保留进一步派生类重写函数B的能力ut不打算替换基类定义。这听起来有点乏味和重复


默认使用覆盖虚拟函数的持续功能有助于重用,但对于一些相对罕见的情况,例如公司库API内部允许一些覆盖,但外部修改被故意阻止,那么在C++11中有一个
final
关键字that表示不允许派生类重写该函数:请参见

它必须重写。它是基类表示的契约的一部分。如果它没有传播到派生类,则将违反OO的原则,例如Liskov替换原则。

oops打字错误。我将修复。B::print也不会使用C::print。什么方法d您希望/希望它使用吗?铁杆人物也会有一点作弊,并使提供所谓“向下钻取行为”的
->
操作员过载这是另一种多态性,但我不知道它是如何实用的,通常是C++编译器用这种技术显示错误的迹象。@ USE248510EEEEK,听起来有点讨厌。我只见过“智能代码指针和迭代器”的代码< >运算符> <代码>重载。(这是合理的,因为它们都像指针一样工作)。你有这种深入行为的例子吗?如果有人关心的话:在C中,你必须在保持虚拟化(关键字覆盖)或反虚拟化功能(关键字新建)之间做出选择.在Java中,任何东西都是虚拟的。@user2485710:我不知道你的建议,但我仍然相当肯定“编译器错误”实际上是“未定义的Bheavior”的例子,即不是编译器中的错误。从什么意义上讲,它会违反LSP?我不同意这一点。另一种方法是要求用户为派生类显式提供
virtual
关键字。不会违反LSP。
#include <iostream>
#include <cstdlib>

using namespace std;

class A{
public:
    A(){
        cout << "construct A" << endl;
    }
    virtual void print(){
        cout << "A says" << endl;
    }
};


class B: public A{
public:    
    B(){
        cout << "construct B" << endl;
    }
    void print(){
        cout << "B says" << endl;
    }    
};

class C: public B{
public:    
    C(){
        cout << "construct C" << endl;
    }
    void print(){
        cout << "C says" << endl;
    }    
    void print(int x){
        cout << "C says " << x << endl;
    }
};


int main(){

    A* ac = new C();
    ac->print();

    B* bc = new C();
    bc->print();

    return 0;
}