【问题标题】:C++ virtual function return typeC++ 虚函数返回类型
【发布时间】:2011-06-07 14:21:45
【问题描述】:

继承的类是否可以实现具有不同返回类型的虚函数(不使用模板作为返回)?

【问题讨论】:

    标签: c++ inheritance virtual-functions overriding return-type


    【解决方案1】:

    在某些情况下,是的,只要返回类型与原始返回类型协变,派生类使用不同的返回类型覆盖虚函数是合法的。例如,考虑以下情况:

    class Base {
    public:
        virtual ~Base() {}
    
        virtual Base* clone() const = 0;
    };
    
    class Derived: public Base {
    public:
        virtual Derived* clone() const {
            return new Derived(*this);
        }
    };
    

    这里,Base 定义了一个名为clone 的纯虚函数,它返回一个Base *。在派生的实现中,这个虚函数被使用Derived *的返回类型覆盖。尽管返回类型与基类中的不同,但这是非常安全的,因为您可以随时编写

    Base* ptr = /* ... */
    Base* clone = ptr->clone();
    

    clone() 的调用将始终返回一个指向Base 对象的指针,因为即使它返回一个Derived*,此指针也可以隐式转换为Base*,并且操作是明确定义的。

    更一般地说,函数的返回类型永远不会被视为其签名的一部分。只要返回类型是协变的,您就可以使用任何返回类型覆盖成员函数。

    【讨论】:

    • 这个“你可以用任何返回类型覆盖一个成员函数”是不正确的。只要返回类型相同或协变(您已解释),您就可以覆盖。这里没有更一般的情况。
    • @bronekk- 您引用的句子的其余部分指出新的返回类型必须在原始类型所在的任何地方都可用;也就是说,新类型与原始类型是协变的。
    • 该句子的其余部分不正确;想象一下用long 替换Base* 和用int 替换Derived*(或者反过来,没关系)。它不会起作用。
    • @bronekk- 是的,我没想到!感谢您指出这一点。
    • 该类型可以在原始类型所在的任何地方使用,并且协方差是两个不同的上下文。 协变意味着实现函数的类型之间的关系和返回的类型之间的关系变化的方式相同。 逆变(虽然在 C++ 中没有用)是相反的上下文。某些语言允许在动态调度中使用逆变参数(如果基类采用 T 类型的对象,则派生类型可以采用 T' ,其中 T' 是 T 的基 - 当您向下一个层次结构时,您会向上移动另一个层次结构)。
    【解决方案2】:

    是的。允许返回类型不同,只要它们是covariant。 C++ 标准是这样描述的(§10.3/5):

    覆盖函数的返回类型应与被覆盖函数的返回类型相同或与函数的类协变。如果函数 D::f 覆盖函数 B::f,则函数的返回类型是协变的,如果满足以下条件:

    • 都是类的指针或类的引用98)
    • B::f 的返回类型中的类与D::f 的返回类型中的类相同,或者,D::f 的返回类型中的类的明确直接或间接基类,并且可通过D 访问
    • 指针或引用都具有相同的 cv 限定,D::f 返回类型中的类类型具有与 B::f 返回类型中的类类型相同或更少的 cv 限定。

    脚注 98 指出“不允许多级指向类的指针或对多级指向类的指针的引用”。

    简而言之,如果DB的子类型,那么D中函数的返回类型需要是B中函数返回类型的子类型。最常见的示例是返回类型本身基于DB,但它们并非必须如此。考虑一下,我们有两个不同的类型层次结构:

    struct Base { /* ... */ };
    struct Derived: public Base { /* ... */ };
    
    struct B {
      virtual Base* func() { return new Base; }
      virtual ~B() { }
    };
    struct D: public B {
      Derived* func() { return new Derived; }
    };
    
    int main() {
      B* b = new D;
      Base* base = b->func();
      delete base;
      delete b;
    }
    

    之所以可行,是因为func 的任何调用者都期待Base 指针。任何Base 指针都可以。因此,如果D::func 承诺始终返回Derived 指针,那么它将始终满足祖先类制定的约定,因为任何Derived 指针都可以隐式转换为Base 指针。因此,调用者总是会得到他们期望的结果。


    除了允许返回类型变化之外,一些语言还允许覆盖函数的参数类型变化。当他们这样做时,他们通常需要逆变。也就是说,如果B::f 接受Derived*,那么D::f 将被允许接受Base*。允许后代在他们将接受的方面宽松,而在他们返回的方面严格。 C++ 不允许参数类型逆变。如果你改变参数类型,C++ 认为它是一个全新的函数,所以你开始重载和隐藏。有关此主题的更多信息,请参阅 Wikipedia 中的 Covariance and contravariance (computer science)

    【讨论】:

    • 这是一个实际功能还是未在解析中使用的返回类型的副作用?
    • @Martin,绝对是一个功能。我很确定重载决议与它无关。如果您要覆盖函数,则使用返回类型
    【解决方案3】:

    虚函数的派生类实现可以有Covariant Return Type

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-10-28
      • 2023-01-20
      • 1970-01-01
      • 2011-07-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多