安全地覆盖C ++虚拟函数


100

我有一个带有虚函数的基类,并且我想在派生类中重写该函数。有什么方法可以使编译器检查我在派生类中声明的函数是否实际上覆盖了基类中的函数?我想添加一些宏或一些确保我不会意外声明新函数的东西,而不是覆盖旧函数。

举个例子:

class parent {
public:
  virtual void handle_event(int something) const {
    // boring default code
  }
};

class child : public parent {
public:
  virtual void handle_event(int something) {
    // new exciting code
  }
};

int main() {
  parent *p = new child();
  p->handle_event(1);
}

这里parent::handle_event()称为而不是child::handle_event(),因为子方法遗漏了const声明,因此声明了一个新方法。这也可能是函数名称的错字或参数类型的细微差别。如果基类的接口发生更改,并且某些派生类未进行更新以反映更改,则也很容易发生这种情况。

有什么方法可以避免此问题,我可以以某种方式告诉编译器或其他工具为我检查吗?任何有用的编译器标志(最好是g ++)?您如何避免这些问题?


2
很好的问题,我一直试图弄清楚为什么我的子类函数不起作用;一个小时以来没有被调用!
Akash Mankar 2014年

Answers:


89

从g ++ 4.7开始,它确实了解新的C ++ 11 override关键字:

class child : public parent {
    public:
      // force handle_event to override a existing function in parent
      // error out if the function with the correct signature does not exist
      void handle_event(int something) override;
};

@hirschhornsalz:我发现当您实现handle_event函数并在函数实现的末尾附加重写时,g ++会出现错误;如果在类声明中的override关键字之后提供内联函数实现,则一切正常。为什么?
2015年

3
override在定义中必须使用@ h9uest 。内联实现既是定义也是实现,所以没关系。
Gunther Piez'1

@hirschhornsalz是的,我通过g ++得到了相同的错误味精。不过请注意:您和g ++错误msg都使用了“类定义”一词-我们不应该使用“声明”({declaration,definition}对)吗?您在特定的上下文中说了“定义和实现”来使自己清晰明了,但是我只是想知道为什么c ++社区突然决定更改类的术语?
h9uest 2015年

20

诸如C#的override关键字之类的内容不属于C ++。

在gcc中,-Woverloaded-virtual警告不要隐藏具有相同名称但签名足够不同且不会覆盖它的基类虚函数。但是,它不能防止由于拼写错误的函数名称本身而无法覆盖函数。


2
如果您碰巧正在使用Visual C ++
Steve Rowe

4
使用Visual C ++不会override在C ++中创建关键字。但这可能意味着您正在使用可以编译某些无效C ++源代码的东西。;)
CB Bailey

3
覆盖无效C ++的事实意味着标准是错误的,而不是Visual C ++
Jon

2
@乔恩:好的,现在我明白了你在开车。我个人可以采用或保留C#样式override功能;我很少遇到覆盖失败的问题,并且相对容易诊断和修复。我认为我不同意的是VC ++用户应该使用它。我希望C ++在所有平台上都像C ++,即使一个特定项目不需要是可移植的。值得一提的是,的C ++ 0x会[[base_check]][[override]][[hiding]]属性,以便您可以选择在如果需要覆盖检查。
CB Bailey 2010年

5
有趣的是,评论后几年采用VC ++不作override关键字现在看来,这确实。嗯,不是一个适当的关键字,而是C ++ 11中的一个特殊标识符。微软推够硬,使这一特殊情况,并遵循属性的通用格式,并override使它成为标准的:)
dribeas大卫-罗德里格斯

18

据我所知,您能否将其抽象化?

class parent {
public:
  virtual void handle_event(int something) const = 0 {
    // boring default code
  }
};

我以为我在www.parashift.com上读到,您实际上可以实现抽象方法。就我个人而言,这是唯一的事情,那就是强制子类来实现它,没有人说过任何不被允许自己实现的事情。


直到现在我才意识到这不仅适用于析构函数!很棒的发现。
斯特拉格

1
这有两个潜在的缺点:1)将一个或多个方法标记为抽象的另一件事是使基类不可实例化,如果这不是该类预期用途的一部分,则可能会出现问题。2)首先,基类可能不是您要修改的。
迈克尔·伯尔

3
我同意迈克尔·伯尔(Michael Burr)的观点。使基类抽象不是问题的一部分。在虚拟方法中具有一个具有功能的基类,而您要派生的类被重写是完全合理的。想要防止其他程序员重命名基类中的函数,并导致派生类不再覆盖它,这也是合理的。在这种情况下,Microsoft“覆盖”扩展名是无价的。我希望看到它添加到标准中,因为不幸的是,没有它就没有好的方法。
布赖恩2012年

这也可以防止BaseClass::method()在派生实现(例如DerivedClass::method())中调用基本方法(例如),例如使用默认值。
Narcolessico

11

在MSVC中,override即使不为CLR进行编译,也可以使用CLR 关键字。

在g ++中,没有在所有情况下都强制执行的直接方法。其他人对于如何使用捕获签名差异给出了很好的答案-Woverloaded-virtual。在将来的版本中,有人可能会__attribute__ ((override))使用C ++ 0x语法添加类似或类似的语法。


9

在MSVC ++中,您可以使用关键字override

class child : public parent {
public:
  virtual void handle_event(int something) <b>override</b> {
    // new exciting code
  }
};

override 适用于MSVC ++中的本机代码和CLR代码。


5

将函数抽象化,以便派生类只能重写它。

@Ray您的代码无效。

class parent {
public:
  virtual void handle_event(int something) const = 0 {
    // boring default code
  }
};

抽象函数不能具有内联定义的主体。必须对其进行修改才能成为

class parent {
public:
  virtual void handle_event(int something) const = 0;
};

void parent::handle_event( int something ) { /* do w/e you want here. */ }

3

我建议您改变一下逻辑。它可能会或可能不会起作用,具体取决于您需要完成的工作。

handle_event()仍然可以执行“无聊的默认代码”,但是不是虚拟的,而是在您希望执行“新的令人兴奋的代码”的时候,让基类调用抽象方法(即必须重写)方法将由您的后代类提供。

编辑:如果您稍后决定你的一些子类的根本不是需要提供“新的令人兴奋的代码”,那么你可以变抽象到虚拟化和提供一个空基类实现的是“插入”功能。


2

您的编译器可能会警告说,如果隐藏了基类函数,则可能会生成该警告。如果有,请启用它。这将捕获const冲突和参数列表中的差异。不幸的是,这不会发现拼写错误。

例如,这是警告Microsoft Visual C ++中的C4263。


1

C ++ 11 override关键字与派生类中的函数声明一起使用时,会强制编译器检查声明的函数是否实际上重写了某些基类函数。否则,编译器将引发错误。

因此,您可以使用override说明符来确保动态多态性(函数覆盖)。

class derived: public base{
public:
  virtual void func_name(int var_name) override {
    // statement
  }
};
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.