在 C++ 中用派生类型覆盖成员函数

Overriding a member function with derived type in C++

我的目标是在 B::g() 中使用 r 作为类型 Y

struct X {};
struct Y : X {};

struct A {
    virtual void f(X q);
    virtual void g(X& r);
    virtual void h(X* s);

    virtual X i();  // note: overridden function is 'virtual X A::i()'
    virtual X* j();
};

struct B final : A {
    void f(Y q) final;   // error: 'void B::f(Y)' marked 'final', but is not virtual
    void g(Y& r) final;  // error: 'void B::g(Y&)' marked 'final', but is not virtual
    void h(Y* s) final;  // error: 'void B::h(Y*)' marked 'final', but is not virtual

    Y i() final;   // error: invalid covariant return type for 'virtual Y B::i()'
    Y* j() final;  // works
};


为了覆盖一个函数参数列表必须相同,但在你的情况下,它不是。这就是为什么它被认为是一个新函数并且不会覆盖父函数的原因。

所以你需要有这样的东西来覆盖:

struct A {
    virtual void f(X q);
    virtual void g(X& r);
    virtual void h(X* s);

    virtual X i();  
    virtual X* j();
};

struct B final : A {
    void f(X q) override final;   
    void g(X& r) override final;  
    void h(X* s) override final;  

    X i() override final;   
    X* j() override final;  
    // Y* j() override final;  <-- Also works because of covariant return
};

但请记住,您仍然可以将 Y 传递给这些函数,因为 Y 继承了 X

作为最后一个 class 成员的旁注 Y* j() override final; 也可以,因为它将是 Covariant return。对于协变 return,您的 return 需要是指针或引用。但是你需要看看你是否真的需要它。

My goal is to use r in B::g() as type Y.

您可以这样做,但是 B::g 不会覆盖 A::g。如果是这样,您需要使用相同的参数类型:

struct A {
    virtual void g(X& r);
    virtual ~A(){}
};

struct B : A {
    void g(X& r) override;
};

要覆盖标记为 final 的内容,您需要在开头添加虚拟。

例如:virtual void f(Y q) final;

B::i() 不起作用,因为 return 类型与您要覆盖的函数的 return 类型不同。

但是 B::j() 起作用的原因是因为 return 类型是一个指针,从技术上讲,您可以 return 一个指向所有类型的指针。

您只能以一种仍然可以安全地使用派生 class 代替基 class 的方式覆盖虚函数(例如,通过指向基的指针)。

让我们以您提供的 classes 为例:

B b;
A *a = &b;

X x;
a->g(x);

在最后一行中,无法确保 g() 的调用者将按 B 的预期传递 Y,因为调用者正在使用基础 class' 接口,这是虚函数和动态多态性的全部要点。

另一方面,return 类型在派生的 class:

中更具体是安全的
B b;
A *a = &b;
X *x = a->j();

最后一条语句仍然是类型安全的,即使 j() 实际上是 returns Y*。此 C++ 功能称为 covariant return type.

您还可以阅读有关类型系统中的协变和逆变的更多信息here

仅当覆盖函数具有与继承函数相同的签名(参数类型、const 限定符等)时才能覆盖虚函数。

所以

 struct A
 {
      virtual void f(X &);
 };

 struct B : public A
 {
      virtual void f(X &);
 };

是可能的,但要更改 f() 的签名,如

 struct B : public A
 {
      virtual void f(Y &);
 };

实际上隐藏了继承的 f() 而不是覆盖它。使用 override 标识符,编译器会检测到这不是覆盖,并诊断错误。

如果正确覆盖,可以将 Y 传递给 A::f()B::f(),因为您的 Y 派生自 X。但是不可能用具有与 A::f().

不同类型参数的函数覆盖 A::f()

有一个函数的特殊情况 returns 一个指向基数 class 的指针(或者,一个引用)——这可以被一个函数覆盖 returns 指向派生 class 的指针。参数必须具有相同类型的约束。因此,您的 B::j() 有效,因为 Y 派生自 X。就编译器而言,像

这样的调用
 X*x = pA->j();    // pA is of type A* but points at a B

没问题,因为它仍然是 returns 一个可以有效转换为 X *

的指针