【问题标题】:Safely override C++ virtual functions安全地覆盖 C++ 虚函数
【发布时间】:2010-10-04 14:25:21
【问题描述】:

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

举个例子:

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++)?您如何避免这些问题?

【问题讨论】:

  • 好问题,我一直在试图弄清楚为什么我的子类函数没有;现在一个小时后没有被调用!

标签: c++ overriding virtual-functions


【解决方案1】:

从 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 关键字之后的类声明中提供内联函数实现,一切都很好。为什么?
  • @h9uest override 必须在定义中使用。内联实现既是定义又是实现,所以没关系。
  • @hirschhornsalz 是的,我得到了 g++ 的相同错误消息。不过,附带说明一下:您和 g++ 错误消息都使用了术语“类定义”——我们不应该使用“声明”({declaration, definition} 对)吗?您通过说“定义和实现”在这个特定的上下文中阐明了自己,但我只是想知道为什么 c++ 社区突然决定更改类的术语?
【解决方案2】:

类似 C# 的 override 关键字不是 C++ 的一部分。

在 gcc 中,-Woverloaded-virtual 警告不要隐藏基类虚函数,该函数具有相同名称但签名完全不同的函数,因此它不会覆盖它。但是,它不会保护您不会因为函数名称本身拼写错误而无法覆盖函数。

【讨论】:

  • 如果你碰巧在使用 Visual C++
  • 使用 Visual C++ 不会使 override 成为 C++ 中的关键字;不过,这可能意味着您使用的东西可以编译一些无效的 C++ 源代码。 ;)
  • override 是无效的 C++ 意味着标准是错误的,而不是 Visual C++
  • @Jon:好的,现在我明白你在说什么了。就我个人而言,我可以接受或放弃 C# 风格的 override 功能;我很少遇到覆盖失败的问题,而且它们相对容易诊断和修复。我不同意的一种观点是 VC++ 用户应该使用它。我希望 C++ 在所有平台上看起来都像 C++,即使一个特定项目不需要是可移植的。值得注意的是,C++0x 将具有 [[base_check]][[override]][[hiding]] 属性,因此您可以根据需要选择覆盖检查。
  • 有趣的是,在评论using VC++ 并没有使override 成为关键字几年后,看来它确实了。好吧,不是正确的关键字,而是 C++11 中的特殊标识符。 Microsoft 竭尽全力对此进行特殊处理,并遵循属性的一般格式,override 将其纳入标准:)
【解决方案3】:

据我所知,你不能把它抽象化吗?

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

我想我在 www.parashift.com 上读到过,您实际上可以实现一个抽象方法。这对我个人来说是有道理的,它唯一做的就是强制子类实现它,没有人说它不允许自己实现。

【讨论】:

  • 直到现在我才意识到这不仅仅适用于析构函数!很棒的发现。
  • 这有几个潜在的缺点:1) 将一个或多个方法标记为抽象的另一件事是使基类不可实例化,如果这不是类的预期用途。 2) 基类可能一开始就不是你要修改的。
  • 我同意迈克尔·伯尔的观点。使基类抽象不是问题的一部分。在虚拟方法中拥有一个具有功能的基类是完全合理的,您希望派生类覆盖它。并且想要防止另一个程序员重命名基类中的函数并导致您的派生类不再覆盖它也是合理的。在这种情况下,Microsoft“覆盖”扩展是无价的。我很乐意看到它被添加到标准中,因为不幸的是,没有它就没有好方法。
  • 这也防止在派生实现(例如DerivedClass::method())中调用基本方法(例如BaseClass::method()),例如为默认值。
【解决方案4】:

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

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

【讨论】:

    【解决方案5】:

    在 MSVC++ 中你可以使用keyword override

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

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

    【讨论】:

      【解决方案6】:

      将函数抽象化,这样派生类就别无选择,只能重写它。

      @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. */ }
      

      【讨论】:

        【解决方案7】:

        我建议对您的逻辑稍作改动。它可能有效,也可能无效,具体取决于您需要完成的任务。

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

        编辑:如果您后来决定您的某些后代类不需要需要提供“新的令人兴奋的代码”,那么您可以将抽象更改为虚拟并提供一个空的基类实现“插入”功能。

        【讨论】:

          【解决方案8】:

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

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

          【讨论】:

            【解决方案9】:

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

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

            class derived: public base{
            public:
              virtual void func_name(int var_name) override {
                // statement
              }
            };
            

            【讨论】:

              【解决方案10】:
              class MyClass {
              public:
               
                MyClass() {}
                virtual uint32_t someFunction(bool param = false) {
                  if (param) {
                    std::cout << "This is an example virtual function with default code" << std::endl;
                  }
                  return 1100;  //just for return something
                };
              
               
              
              • 然后您可以根据需要覆盖该函数
              class MyClass2 : public MyClass  {
              public:
                MyClass2();
                uint32_t someFunction(bool param) override;
              };
              
              
              
              uint32_t MyClass2::someFunction(bool verbose) {
                std::cout << "This is new implementation for virtual method " << std::endl;
              
              }
              
              

              【讨论】:

                猜你喜欢
                • 2015-06-17
                • 2020-08-21
                • 1970-01-01
                • 1970-01-01
                • 2014-05-22
                • 2013-10-04
                • 2013-01-16
                • 2021-09-30
                • 2015-12-15
                相关资源
                最近更新 更多