【问题标题】:Why do I have to specify pure virtual functions in the declaration of a derived class in Visual C++?为什么我必须在 Visual C++ 的派生类声明中指定纯虚函数?
【发布时间】:2010-04-19 10:35:30
【问题描述】:

给定基类 A 和派生类 B:

class A {
public:
  virtual void f() = 0;
};

class B : public A {
public:
  void g();
};

void B::g() {
    cout << "Yay!";
}

void B::f() {
    cout << "Argh!";
}

在尝试定义 void B::f() 时,我收到错误消息说 f() 未在 B 中声明。我必须在 B 中明确声明 f() 吗?我认为如果接口发生变化,我不应该更正从它派生的每个类中的声明。 B 有没有办法自动从 A 获取所有虚函数的声明?

编辑:我发现一篇文章说纯虚函数的继承取决于编译器: http://www.objectmentor.com/resources/articles/abcpvf.pdf

我用的是VC++2008,不知道有没有这个选项。

【问题讨论】:

    标签: c++ visual-c++ virtual definition


    【解决方案1】:

    我必须在 B 中显式声明 f() 吗?

    是的,您必须在类的定义中声明要在类中覆盖的任何基类的所有虚函数。至于为什么:这就是 C++ 语法的方式。
    注意virtual关键字可以省略重写虚函数的声明:

    class base {
      virtual void f();
      virtual void g();
    };
    
    class derived : public base {
      virtual void f(); // overrides base::f()
      void g();         // overrides base::g()
    };
    

    注意:一个类声明是这样的:class my_class;,而这个class my_class { /* ... */ };是一个类定义。您可以对仅声明但未定义的类执行有限数量的操作。特别是,您不能创建它的实例或调用成员函数。
    有关声明和定义之间差异的更多信息,请参阅here

    好的,为了方便 cmets 中发生的“声明与定义”辩论,这里引用 C++03 标准 3.1/2:

    声明是一个定义,除非它 [...] 是一个类名声明 [...]。

    3.1/3 然后举几个例子。其中:

    [例如:[...]
    struct S { int a; int b; }; // defines S, S::a, and S::b
    [...]
    struct S; // declares S
    ——结束示例]

    总结一下:C++ 标准认为struct S; 是一个声明,struct S { /*...*/ }; 是一个定义。我认为这是对我对 C++ 中类的“声明与定义”的解释的有力支持。

    【讨论】:

    • 其实你提到的两个都是类声明。没有正文的声明是前向声明。类定义是类声明的实现(.cpp 文件中的函数实现)。
    • @tames:实际上,你错了。前向声明是我们在 C++ 中唯一的类声明。 (是的,“转发”是多余的。)除其他外,类定义包含成员函数声明(有时对于类定义中定义的内联函数,成员函数定义。您在 cpp 文件中编写的内容主要是成员函数定义(加上类定义中声明的静态类成员的定义)。参见stackoverflow.com/questions/1410563/1410632#1410632stackoverflow.com/questions/671925/671936#671936
    • @sbi:这是一个有趣的话题。我的理解以及我发现的大多数文档都将其称为类声明;并且作为声明,它可以出现在多个编译单元中(只要它相同),而类定义(我的用法)只能出现一次。但是,一些文档说完整的类声明 class C {...} 既是声明又是定义;前向声明允许使用类名,但该类尚未完全声明。
    • @tames:如果大多数文档确实按照您所说的方式描述它,那么大多数文档描述它是错误的。在 C++ 中(这很重要,因为不同语言的规则不同,在某些方面 C 与 C++ 甚至略有不同)class x; 是一个 类声明 (因此可以在will),而class x { /*...*/ }; 是一个类定义(因此每个翻译单元只能出现一次)。 TTBOMK,这是术语“声明”和“定义”对 C++ 类的唯一正确应用。
    • @sbi:我不想强调这一点,但完整的类声明指定了类“类型”并出现在它使用的每个编译单元中(每个文件都包含头文件) ,因此,它出现多次(不允许定义)......但类定义出现在一个且只有一个源文件中。前向声明只是一种特殊情况声明,允许在完全声明类之前对其进行引用。
    【解决方案2】:

    是的,在 C++ 中,您必须通过在派生类中声明(和定义)基类方法的行为来明确说明您覆盖基类方法行为的意图。如果你试图在派生类中提供一个新的实现而不在类定义中声明它,这将是一个编译器错误。

    【讨论】:

    • 好吧,我的错,我忘了提到该函数是 pure 虚拟的,现在应该清楚它必须由派生类,我仍然得到错误。
    • @neuviemeporte:同样的规则也适用于纯虚拟。想象一下,您正在构建这样的 A->B->C 类层次结构。在这种情况下,在 B 中自动定义虚(或纯虚函数)是没有意义的,因为 B 本身可能是一个抽象类,向 A 添加了更多纯虚方法。
    • B中的声明应该也有virtual关键字吗?我已经看到了这两种方式。
    • @neuviemeporte:不需要。但我发现将函数编写为虚拟函数是一个好习惯。忽略它你不会得到任何东西。一旦它在基类中被声明为虚拟,它在派生类中也仍然是虚拟的。
    【解决方案3】:

    C++ 类声明定义了类的内容。如果你没有在 B 中声明 f(),看起来你没有覆盖它。 B::f()只有声明才能实现。

    【讨论】:

      【解决方案4】:

      在您当前的代码中,您只是继承了 B 类中的函数 f(),并没有在派生类中重新定义基类的成员。

      【讨论】:

      • 好吧,他写道“我收到错误说 f() 没有在 B 中声明,而尝试定义 void B::f()”,所以我想可以安全地假设他正在尝试定义B::f()
      • 绝对......我只是想给他一些额外的信息,因为他试图做的是在他的派生类中重新定义基类方法。
      【解决方案5】:

      有没有办法让B得到所有的 来自 A 的虚函数声明 自动?

      如果您没有将 A 中的函数 f 标记为 pure 虚且 =0,则该函数也会自动出现在任何子类中。

      class A {
      public:
        virtual void f(); // not =0!
      };
      
      class B : public A {
      public:
        void g();
      };
      
      void A::f() {
          cout << "I am A::f!";
      }
      
      void B::g() {
          cout << "Yay!";
      }
      

      现在:

      B* b = new B();
      b->f(); // calls A::f
      

      【讨论】:

        【解决方案6】:

        通过声明一个纯虚函数,您是在声明您的类是抽象的,并且您希望所有具体派生类都具有该函数的实现。不提供纯虚函数实现的派生类是抽象基类的扩展,它本身就是一个抽象类。尝试实例化抽象类当然是错误的。

        纯虚函数允许您定义一个您希望所有派生类都遵守的接口“契约”。该类的客户端可以期望任何具有该接口的实例化类实现合同中的功能。

        另一个有趣的花絮...您可以为纯虚函数提供一个主体,但它仍然是纯的,并且必须在具体派生类中被覆盖。提供主体的优点是提供基本行为,同时仍然强制派生类实现该功能。然后,被覆盖的函数可以像其他虚函数一样调用基函数 Base::F()。当没有定义 body 时,在纯虚函数上调用 Base::F() 是错误的。

        【讨论】:

          猜你喜欢
          • 2011-08-06
          • 2017-02-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-07-16
          • 2015-03-18
          • 2018-01-25
          相关资源
          最近更新 更多