浅谈C++中的虚拟函数重写

浅谈C++中的虚拟函数重写,c++,inheritance,polymorphism,overriding,virtual,C++,Inheritance,Polymorphism,Overriding,Virtual,我对以下情况有点困惑,它们是重写函数副本而不是重载的正确方法,还是所有这些方法都是正确的 class Base{ public: virtual Base* copy(Base* b){...} }; class Derived:public Base{ public: virtual Base* copy(Base* b){...}//I know this should work // but how about the followings? //virtual

我对以下情况有点困惑,它们是重写函数副本而不是重载的正确方法,还是所有这些方法都是正确的

class Base{
public:
    virtual Base* copy(Base* b){...}
};

class Derived:public Base{
public:
    virtual Base* copy(Base* b){...}//I know this should work
 // but how about the followings?
    //virtual Base* copy(Derived* b){...}
    //virtual Derived* copy(Base* b){...}
    //virtual Derived* copy(Derived* b){...}
};
顺便问一下,访问权的改变有什么区别吗?比如说,我这样编写派生类:

class Derived:public Base{
private://or protected:
    virtual Base* copy(Base* b){...}
    ...
};

以下是函数重写的规则:

[C++11:10.3/2]:如果虚拟成员函数vf在类基中声明,并且在派生的类中,直接或间接地从基中派生,则声明具有相同名称、参数类型列表8.3.5、cv限定符和ref限定符的成员函数vf,或者没有与Base::vf相同的限定符,然后,派生::vf也是虚拟的,不管它是否如此声明,并且它重写了111 Base::vf。[……]

如果不满足这些规则,则新函数不会重写旧函数,尽管它可能会重载或隐藏旧函数

因此:

尽管如此,请注意,由于10.3/2的结尾说明:

在派生类中,如果基类子对象的虚拟成员函数具有多个最终重写器,则程序格式错误

这意味着我们应该只声明其中一个覆盖函数。我把它们都列在一个类定义中只是为了说明

虚拟派生的*copyBase*b重写Base::copy可能会令人惊讶,因为它具有不同的返回类型;只要两种返回类型为:

[C++11:10.3/7]:重写函数的返回类型应与重写函数的返回类型相同,或与函数类共变。如果函数D::f重写函数B::f,则函数的返回类型是协变的,前提是它们满足以下条件:

它们都是指向类的指针,都是对类的左值引用,或者都是对类的右值引用 B::f的返回类型中的类与D::f的返回类型中的类相同,或者是D::f的返回类型中的类的明确且可访问的直接或间接基类 两个指针或引用都具有相同的cv限定,并且D::f的返回类型中的类类型具有与B::f的返回类型中的类类型相同的cv限定,或者小于B::f的返回类型中的类类型。 至于公共与私人的问题,没有规则说这很重要;如果有任何疑问,脚注111将对情况进行澄清:

111具有相同名称但不同参数列表的函数作为虚函数不一定是虚函数,也不重写。在重写函数的声明中使用虚拟说明符是合法的,但冗余具有空语义。在确定覆盖时未考虑访问控制第11条


它们都是法律声明,只是这两个

virtual Base* copy(Derived* b);
virtual Derived* copy(Derived* b);
不要重写基类的副本,因为它们的签名不同。他们只是声明了一个新的虚拟副本,该副本会将该副本隐藏起来。 然而,这一次

virtual Derived* copy(Base* b);
不覆盖。它有相同的签名和一个字母

在C++11中,如果函数未重写任何内容,则可以使用override强制编译器发出错误:

virtual Derived* copy(Derived*) override { /*...  */} // will produce an error
访问权限没有任何直接区别——它是根据对象的静态类型进行检查的。如果基类中的副本是公共的,并且您通过指向基类的指针调用它,那么即使它是私有的,它也会调用适当的重写函数

class Base {
public:
    virtual Base* copy(Base* b);
};

class Derived : public Base {
private:
    virtual Base* copy(Base* b);       // Overrides Base::copy
};

int main()
{
    Base* b = new Derived;
    Base* b2;
    b->copy(b2); // calls Derived::copy
    Derived d;
    d.copy(b2); // error, as expected
}

在我写这篇文章的时候,已经有了两个很好的答案,但我还是提交了我的答案,因为它是用另一种风格写的。也许这个肤浅的答案对某些人有用

首先,复制方法是对象的一部分,将对象作为输入,然后返回对象,这有点不清楚。它是从输入中复制还是复制到输入中?它是返回副本还是自身?它应该是静态的吗

您所有的声明都取决于您希望实现的目标,但并不是所有声明一起起作用

编辑:我删除了评论中有争议的部分,其他答案也涵盖了这一点。但我保留了给出示例的部分,以解释为什么不允许返回类型上的多态性

要仅在派生中使用实现,可以声明

class Derived:public Base{
public:
    virtual Derived* copy(Base* b){...}; 
    virtual Derived* copy(Derived* b){}; 
};

在C++中不支持基于返回类型的多态性。你不能使用

因为如果不使用结果,编译器将很难确定要做什么。考虑:

Derived * d = new Derived(); 

Derived * toCopy = new Derived(); 

Base * b2 = toCopy->copy(d); // Should use use the version returning Base

Derived * d2 = toCopy->copy(d); // Should use the version returning Derived

toCopy->copy(d2); // Which implementation should the compiler pick? It cannot know!
由于编译器无法决定要在上面最后一行中使用的版本,因此在返回类型上重载是非法的


至于访问权,我很乐意推荐其他答案

我不明白你的问题。你能重新措辞吗?您是在询问重写虚拟函数的规则吗?是的,我现在重新表述。但是,您不能在派生中同时使用派生的*copyBase*b和Base*copyBase*b。协方差不存在
这并不意味着可以根据返回类型使用多态性。@dan.p:我的回答已经解释了只允许使用一个最终重写器。当然,你是对的。啊,对不起。我在所有有趣的东西中都忽略了它;和派生的toCopy=新派生的;对于任何函数来说,在返回类型上重载都是非法的,不管它是虚拟的还是非虚拟的。也可以是Base b2=toCopy.copyd;也是非法的。@jrok,谢谢您指出错误。返回类型上的重载是非法的,这正是我试图解释的。我试图澄清。@Ghostblade您可以在派生的基础上调用toCopyInto->Base*。将调用的是Base::copy,而不是派生的::copy。因为在这种情况下Base:copyBase*不被重写。@dan.p基类的副本由派生类隐藏,但不被重写,所以如果要从派生类的指针调用Base的副本,应将using Base::copy放在类中body@dan.p这意味着您的toCopyInto->copyb将无法编译
class Derived:public Base{
public:
    virtual Base* copy(Base* b){...}; 
    virtual Derived* copy(Derived* b){}; 
};
class Derived:public Base{
public:
    virtual Base* copy(Derived* b){...}; 
    virtual Derived* copy(Derived* b){}; 
};
Derived * d = new Derived(); 

Derived * toCopy = new Derived(); 

Base * b2 = toCopy->copy(d); // Should use use the version returning Base

Derived * d2 = toCopy->copy(d); // Should use the version returning Derived

toCopy->copy(d2); // Which implementation should the compiler pick? It cannot know!