【问题标题】:Question on Virtual and default arguments关于虚拟和默认参数的问题
【发布时间】:2011-07-21 08:54:04
【问题描述】:

我想确认以下几点:

虚拟机制:

如果我有一个基类A并且它有一个Virtual方法,那么在派生类中,我们一般不会在函数声明中包含virtual语句。但是,当包含在派生类定义中时,虚拟意味着什么。

class A
{
public: 
virtual void something();
}

class B:public A
{
public:
virtual void something();
}

这是否意味着我们要覆盖从类 B 派生的类中的某个方法?

另外,还有一个问题,

我有一个类A,它是由三个不同的类派生的。现在,在基类A中有一个虚方法anything()。

现在,如果我要在基类 A::anything() 中为该方法添加一个新的默认参数,我需要将它添加到所有 3 个类中。

我的答案:

  1. 如果基类中的虚拟方法在派生类中被重新定义为虚拟,那么我们可能意味着它应在使用该类作为基类的相应派生类中被覆盖。
  2. 是的。如果不覆盖没有任何意义。

如果我的感觉(以上 2)正确,请告诉我。

谢谢, 帕万呻吟。

【问题讨论】:

    标签: c++ virtual overriding


    【解决方案1】:

    virtual 关键字可以在派生类的覆盖中省略。如果基类中被覆盖的函数是虚拟的,那么覆盖也被假定为虚拟的。

    这个问题很好地涵盖了这一点:In C++, is a function automatically virtual if it overrides a virtual function?


    您的第二个问题是关于默认值和虚函数。基本上,每个覆盖都可以有不同的默认值。但是,通常这不会像您期望的那样做,所以我的建议是:不要混合使用默认值和虚函数

    基类函数是否默认,与派生类函数是否默认完全无关。

    基本思想是静态类型将用于查找默认值(如果已定义)。对于虚函数,动态类型将用于查找被调用函数。

    所以当动态和静态类型不匹配时,就会出现意想不到的结果。

    例如

    #include <iostream>
    
    class A
    {
    public:
      virtual void foo(int n = 1) { std::cout << "A::foo(" << n << ")" << std::endl; }
    };
    
    class B : public A
    {
    public:
      virtual void foo(int n = 2) { std::cout << "B::foo(" << n << ")" << std::endl; }
    };
    
    int main()
    {
      A a;
      B b;
    
      a.foo(); // prints "A::foo(1)";
      b.foo(); // prints "B::foo(2)";
    
      A& ref = b;
      ref.foo(); // prints "B::foo(1)";
    }
    

    如果您的所有覆盖共享相同的默认值,另一种解决方案是在基类中定义一个附加函数,该函数只使用默认参数调用虚函数。那就是:

    class A
    {
    public:
       void defaultFoo() { foo(1); }
       virtual void foo(int n) { .... }
    };
    

    如果您的覆盖具有不同的默认值,您有两种选择:

    • 也将defaultFoo() 设为虚拟,如果派生类重载一个而不重载另一个,这可能会导致意外结果。
    • 不要使用默认值,而是在每次调用中明确声明使用的值。

    我更喜欢后者。

    【讨论】:

    • 是的,选择哪个默认值仅基于调用该方法的对象的静态类型。这可能正是如此,或者完全出乎意料。我同意你的建议……只要对虚函数的默认值说“不”。
    【解决方案2】:

    在派生类中是否写virtual都没有关系,因为基类,它总是虚拟的,但是最好包含virtual以明确声明它是虚拟的,然后如果您不小心从基类中删除了该关键字,它将给您编译器错误(您不能用虚拟函数重新定义非虚拟函数)。编辑>>对不起,我错了。您可以使用虚拟函数重新定义非虚拟函数,但是一旦它是虚拟的,即使您不编写 virtual 关键字,所有具有相同签名的派生类的函数也将是虚拟的。

    如果您不重新定义虚函数,则将使用基类中的定义(就好像它是逐字复制的一样)。

    如果您希望指定应在派生类中重新定义虚函数,则不应提供任何实现,即virtual void something() = 0; 在这种情况下,您的类将是一个抽象基类 (ABC),并且不能从中实例化任何对象。如果派生类不提供它自己的实现,它也将是一个 ABC。

    我不确定默认参数是什么意思,但函数签名应该匹配,因此所有参数和返回值都应该相同(最好不要将重载/默认参数与继承混合,因为你会得到非常令人惊讶的结果示例:

    class A
    {
    public: 
    void f(int x);
    };
    
    class B:public A
    {
    public:
    void f(float x);
    };
    
    int main() {
    B b;
    b.f(42); //this will call B::f(float) even though 42 is int
    }
    

    【讨论】:

      【解决方案3】:

      这里有一个小实验来测试你想知道什么:

      class A {
      public:
              virtual void func( const char* arg = "A's default arg" ) {
                      cout << "A::func( " << arg << " )" << endl;
              }
      };
      
      class B : public A {
      public:
              void func( const char* arg = "B's default arg" ) {
                      cout << "B::func( " << arg << " )" << endl;
              }
      };
      
      class C : public B {
      public:
              void func( const char* arg ) {
                      cout << "C::func( " << arg << " )" << endl;
              }
      };
      
      int main(int argc, char* argv[])
      {
              B* b = new B();
              A* b2 = b;
              A* c = new C();
              b->func();
              b2->func();
              c->func();
              return 0;
      }
      

      结果:

      B::func( B's default arg )
      B::func( A's default arg )
      C::func( A's default arg )
      

      结论:

      1- A 的 func 声明之前的 virtual 关键字使得该函数在 B 和 C 中也是虚拟的。

      2- 使用的默认参数是在用于访问对象的指针/引用类中声明的参数。

      【讨论】:

        【解决方案4】:

        正如有人指出的那样,派生类中与基类中的虚函数具有相同名称和类型签名的函数自动始终是虚函数。

        但是您关于默认参数的第二个问题很有趣。这是一个思考问题的工具...

        class A {
          public:
           virtual void do_stuff_with_defaults(int a = 5, char foo = 'c');
        };
        

        几乎等同于:

        class A {
          public:
           virtual void do_stuff_with_defaults(int a, char foo);
        
           void do_stuff_with_defaults() { // Note lack of virtual keyword
              do_stuff_with_defaults(5, 'c'); // Calls virtual function
           }
        
           void do_stuff_with_defaults(int a) { // Note lack of virtual keyword
              do_stuff_with_defaults(a, 'c'); // Calls virtual functions
           }
        };
        

        因此,如果你给你的虚函数提供默认参数,那么你基本上拥有在类中声明的具有相同名称但不同类型签名的虚函数和非虚函数。

        在方式上,它不等同于能够使用using 指令从基类导入名称。如果将默认参数声明为单独的函数,则可以使用 using 指令导入这些函数。如果您只是声明默认参数,则不是。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-02-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-01-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多