【问题标题】:About virtual function override in C++关于 C++ 中的虚函数覆盖
【发布时间】:2013-08-24 16:26:24
【问题描述】:

我对以下情况有点困惑,它们是覆盖函数copy但不重载的正确方法,还是全部正确?

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++ inheritance polymorphism overriding virtual


【解决方案1】:

这些是函数覆盖的规则:

[C++11: 10.3/2]: 如果在类Base 和类Derived 中声明一个虚成员函数vf,直接或间接派生自Base,则成员函数vf声明了与 Base::vf 相同的名称、参数类型列表 (8.3.5)、cv-qualification 和 ref-qualifier(或没有相同),则 Derived::vf 也是虚拟的(无论是否它是如此声明)并且它覆盖111Base::vf[..]

如果不满足这些规则,则新函数不会覆盖旧函数(尽管它可能重载隐藏它) .

所以:

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

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

    // Does NOT override Base::copy (due to different parameter-type-list)
    virtual Base* copy(Derived* b);

    // Overrides Base::copy (despite different return type)
    virtual Derived* copy(Base* b);

    // Does NOT override Base::copy (due to different parameter-type-list)
    virtual Derived* copy(Derived* b);

private:
    // Overrides Base::copy (despite different access specifier)
    virtual Base* copy(Base* b);
};

不过,请注意,上述 Derived 类实际上是格式错误的,因为 10.3/2 的结尾指出:

在派生类中,如果基类子对象的虚成员函数具有多个最终覆盖器,则程序是非良构的。

这意味着我们应该只声明那些覆盖函数中的一个。我将它们全部列在一个类定义中只是为了便于说明。

virtual Derived* copy(Base* b) 覆盖 Base::copy 可能会令人惊讶,因为它具有不同的返回类型;只要两个返回类型是 covariant,这是允许的:

[C++11: 10.3/7]: 覆盖函数的返回类型应与被覆盖函数的返回类型相同或协变与函数的类。如果函数D::f 覆盖函数B::f,如果满足以下条件,函数的返回类型是协变的:

  • 都是指向类的指针,都是对类的左值引用,或者都是对类的右值引用
  • B::f返回类型中的类与D::f返回类型中的类是同一类,或者是@987654341返回类型中的类的明确且可访问的直接或间接基类@
  • 指针或引用都具有相同的 cv 限定,D::f 返回类型中的类类型具有与 B::f 返回类型中的类类型相同或更少的 cv 限定。

至于publicprivate 的问题,没有规定说这很重要;如果有任何疑问,脚注 111 澄清了这种情况:

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

【讨论】:

  • 但是,Derived* copy(Base* b)Base* copy(Base* b) 中的Derived 不能同时拥有。协方差并不意味着您可以根据返回类型进行多态性。
  • @dan.p:我的回答已经解释了只允许一个最终覆盖。当然,你是对的。
  • 啊,对不起。我错过了它嵌入所有有趣的东西。
【解决方案2】:

都是合法的声明,只是这两个

virtual Base* copy(Derived* b);
virtual Derived* copy(Derived* b);

不要覆盖基类中的copy,因为它们的签名不同。他们只是声明了一个新的虚拟copy,它隐藏了基地。
不过这个

virtual Derived* copy(Base* b);

确实覆盖。它有相同的签名和covariant return type

在 C++11 中,如果函数没有覆盖任何内容,您可以使用 override 强制编译器发出错误:

virtual Derived* copy(Derived*) override { /*...  */} // will produce an error

访问权限没有任何直接的区别——它是根据对象的静态类型来检查的。如果 base 中的 copy 是公共的,并且您通过指向基类的指针调用它,即使它是私有的,它也会调用合适的覆盖函数。

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
}

【讨论】:

    【解决方案3】:

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

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

    您的所有声明都“有效”(取决于您希望实现的目标),但并非所有声明都在一起。

    编辑:我删除了 cmets 中有争议的部分,其他答案无论如何都涵盖了。但我保留部分给出一个示例来解释为什么不允许返回类型的多态性。

    要仅使用Derived 中的实现,您可以声明

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

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

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

    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!
    

    因为编译器无法决定上面最后一行使用的版本,所以在返回类型上重载是非法的。

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

    【讨论】:

    • 您有两个错误:Derived toCopyInto = new Derived();Derived toCopy = new Derived(); 对于任何函数,无论是否为虚拟函数,返回类型的重载都是非法的。 Base b2 = toCopy.copy(d); 也是非法的。
    • @jrok,感谢您指出错误。返回类型的重载是非法的,这是我试图解释的。我试图澄清。
    • @Ghostblade 你可以在Derived 上调用toCopyInto->(Base *)。将调用的是Base::copy 而不是Derived::copy。因为Base:copy(Base *) 在这种情况下没有被覆盖。
    • @dan.p 基类的 copy 被 Derived 隐藏但没有被覆盖,所以如果你想从 Derived 类的指针调用 Base 的副本,你应该把 using Base::copy 放在你的类体中
    • @dan.p 这意味着你的toCopyInto->copy(b) 不会编译
    猜你喜欢
    • 1970-01-01
    • 2015-06-17
    • 2020-08-21
    • 1970-01-01
    • 1970-01-01
    • 2014-05-22
    • 2020-12-16
    • 2013-10-04
    • 2010-10-04
    相关资源
    最近更新 更多