为什么在重写虚函数时需要重新声明[C++]

30
#include <iostream>
using namespace std;

class Duck {
public:
        virtual void quack() = 0;
};

class BigDuck : public Duck {
public:
  //  void quack();   (uncommenting will make it compile)

};

void BigDuck::quack(){ cout << "BigDuckDuck::Quack\n"; }

int main() {
        BigDuck b;
        Duck *d = &b;
        d->quack();

}

上面的代码无法编译。但是,当我在子类中声明虚函数时,它就可以编译了。

如果编译器已经有了子类将覆盖的函数的签名,那么为什么还需要重新声明呢?

有什么见解吗?


2
你遇到了什么编译器错误? - Justin Ethier
尽管在这个例子中我们看到了一个抽象基类,但是这个问题通常也是有效的。 - xtofl
我可以尝试着帮你解决这个问题,但是如果我不知道你正在使用哪个编译器,那么我可能无法获得相同的(或任何)错误。 :) - Justin Ethier
7个回答

25

需要重新声明是因为:

  • 标准要求如此。
  • 这样可以使编译器的工作更加轻松,不需要向上爬升层次结构以检查是否存在该函数。
  • 您可能希望在层次结构中较低的位置声明它。
  • 为了实例化类,编译器必须知道这个对象是具体的。

1
声明末尾的“= 0;”意味着您必须在派生类中定义它,而不是您可能想要这样做。 - Ben Burnett
1
@Ben:你可能会决定在特定项目中不想实例化那个类 :) 但你是正确的。 我所指的是,你可以选择在继承层次结构中更低的层次上实现它,甚至更低。 - the_drow
3
需要重新声明是因为[...]。这可以使编译器的工作更加简单 - 噢,该死的 C++,为什么不让每个人都用汇编语言写代码呢?那样会让编译器的工作容易得多。 - Ivan Ivanov

12
如果你更改了:
virtual void quack() = 0;

virtual void quack();

如果不在HugeDuck中实现quack()函数,代码将会编译通过。

函数声明末尾的“=0;”实际上是在声明所有BigDucks都能quack,但是必须由每个派生鸭子类来实现。如果删除“= 0;”,则会调用BigDuck的quack函数,除非你在HugeDuck中实现了quack。

编辑:需要澄清的是,“=0;”表示派生类将具有该函数的定义。在您的示例中,它期望HugeDuck定义quack(),但是由于您将其注释掉了,因此没有定义。

另外,既然所有鸭子都能quack,也许我们看不到的原始Duck类应该实现quack函数?


HugeDuck == BigDuck 吗?这是您提出的第二个派生类,还是打字错误?(或者是 BigDuck 的派生类,或其他什么东西)? - Mike H-R
是的,但这不是 OP 的问题。问题是为什么需要声明 BigDuck::quack()。如果已经定义BigDuck::quack(),则无论 Duck::quack() 是否为纯虚拟函数,在 BigDuck 的成员说明中都必须声明 quack(),即使在 Duck 中已经声明过。 - RBF06

7

由于C++将“声明”与“多态性”分开:任何函数都需要编译器的声明,无论它是否是虚拟的。

您的示例不够完整,存在“抽象类”问题:BigDuck无法实例化,因为它在接口中没有quack的实现。

概括问题,我们可以声明基本函数纯虚函数:

class Duck { public: virtual void quack(){} };

class BigDuck : public Duck {}; 

// WRONG: undeclared method definition
void BigDuck::quack(){ cout << "QUACK!"; }

在这里,编译器会抱怨它有一个未声明的符号BigDuck::quack。这与抽象类或其他任何东西无关。

(注意:gcc说: error: no 'void BigDuck::q()' member function declared in class 'BigDuck' )


这个好像不行了?在使用GCC 11时出现“error: no declaration matches ‘void BigDuck::quack()’”错误。 - grandchild
似乎无论是虚拟的还是非虚拟的,如果你想在派生类中实现该方法,都必须重新声明。但我并不完全确定其有效性。 - grandchild
1
我会添加一条注释,让你知道必须读过代码后才能发现这段代码是一个不可编译的示例。 - xtofl

2
您的基类中Quack()的定义是“抽象的”——它没有实现。这告诉编译器您的派生类必须实现它。如果未能这样做,将导致编译错误。

但是编译器肯定知道在实现中需要它。提取声明有什么作用呢? - Matt Ellen
@Matt 语言标准规定必须这样做 - 这与抽象类无关,所有虚函数(实际上所有函数)都是这样工作的。 - anon
自从我“愤怒地”使用C ++做过任何事情以来已经过了好几年,但我相当确定该代码实际上没有为quack()提供实现,而是要么被忽略了(因为编译器不希望有方法实现),要么是实现了一个新方法(隐藏/重载而不是覆盖)。 - GalacticCowboy
@Galactic 它正在派生类中提供一种实现。它肯定不会被忽略! - anon
我撤回我的声明。 :) 我的意思是他写的方式,但当然它不会编译,所以我的声明两部分都不正确。如果它不能编译,它既没有被忽略也没有重载。 - GalacticCowboy

2

BigDuck可以是另一个抽象类,您可能不想在到达基类ReallyBigDuck之前实现quack方法。


现在这是一个不声明 quack 的好理由。但是为什么要强制声明它呢? - xtofl

2

在提供实现之前,所有继承自包含 Pure Virtual Function 的类都是抽象的 - 不能被实例化。为了提供这样的实现,您必须在类中声明该函数。


1
在每个类中声明方法将告诉编译器该类为该方法提供了不同的实现。
此外,如果您想在堆栈上创建BigDuck对象,那么编译器应该如何知道quack()的签名。
BigDuck aDuck;
aDuck.quack();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接