【问题标题】:virtual function default arguments behaviour虚函数默认参数行为
【发布时间】:2021-08-27 18:45:20
【问题描述】:

我对以下代码有一个奇怪的情况。请帮我澄清一下。

class B
{
       public:
            B();
            virtual void print(int data=10)
            {
                  cout << endl << "B--data=" << data;
            }
};
class D:public B
{
       public:
            D();
            void print(int data=20)
            {
                  cout << endl << "D--data=" << data;
            }
};

int main()
{
     B *bp = new D();
     bp->print();
return 0;
}

关于我期望的输出

[ D--data=20 ]

但实际上是这样的

[ D--data=10 ]

请帮忙。这对你来说似乎很明显,但我不知道内部机制。

【问题讨论】:

  • 如果某个答案解决了您的问题(或让您理解了它),请使用答案左侧的绿色勾号接受它。

标签: c++


【解决方案1】:

基本上,当你用这样的默认参数声明一个函数时,你会(隐式地)声明和定义一个内联重载,它只使用一个更少的参数来调用具有该参数值的完整函数。问题是,这个额外的重载函数是 not 虚拟的,即使该函数是。所以你在 B 中定义的函数等价于:

        virtual void print(int data)
        {
              cout << endl << "B--data=" << data;
        }
        void print() { print(10); }

这意味着当您调用print()(不带参数)时,您获得的函数是基于静态类型的(如果您感到困惑,则为 B)。然后调用print(int),它是虚拟的,所以使用动态类型。

如果您希望此默认参数为虚拟,则需要显式定义重载函数(作为虚拟)以使其工作。

【讨论】:

    【解决方案2】:

    默认参数完全是编译时特性。 IE。在编译时执行默认参数替换缺失参数。出于这个原因,显然,成员函数的默认参数选择不可能依赖于对象的 dynamic(即运行时)类型。它始终取决于对象的 static(即编译时)类型。

    您在代码示例中编写的调用会立即被编译器解释为bp-&gt;print(10),而不考虑其他任何内容。

    【讨论】:

    • 感谢安德烈为我解惑。我没有朝这个方向思考。非常感谢。
    【解决方案3】:

    动态绑定使用 vpointer 和 vtable。但是,动态绑定只适用于函数指针。没有动态绑定参数的机制。

    因此,默认参数是在编译器时静态确定的。在这种情况下,它是由 bp 类型静态确定的,它是指向 Base 类的指针。因此 data = 10 作为函数参数传递,而函数指针指向派生类成员函数:D::print。本质上,它调用 D::print(10)。

    以下代码 sn-p 和结果输出清楚地说明了这一点:即使它调用 Derived 调用成员函数 Derived::resize(int),它也传递了 Base 类默认参数:size=0。

    virtual void Derived::resize(int) size 0

    #include <iostream>
    #include <stdio.h>
    using namespace std;
    
    #define pr_dbgc(fmt,args...) \
        printf("%d %s " fmt "\n",__LINE__,__PRETTY_FUNCTION__, ##args);
    
    class Base {
       public:
           virtual void resize(int size=0){
               pr_dbgc("size %d",size);
           }
    };
    
    class Derived : public Base {
       public:
           void resize(int size=3){
               pr_dbgc("size %d",size);
           }
    };
    
    int main()
    {   
        Base * base_p = new Base;
        Derived * derived_p = new Derived;
    
        base_p->resize();           /* calling base member function   
                                       resize with default
                                       argument value --- size 0 */
        derived_p->resize();        /* calling derived member      
                                       function resize with default 
                                       argument default --- size 3 */
    
        base_p = derived_p;         /* dynamic binding using vpointer 
                                       and vtable */
                                    /* however, this dynamic binding only
                                       applied to function pointer. 
                                       There is no mechanism to dynamic 
                                       binding argument. */
                                    /* So, the default argument is determined
                                       statically by base_p type,
                                       which is pointer to base class. Thus
                                       size = 0 is passed as function 
                                       argument */
    
        base_p->resize();           /* polymorphism: calling derived class   
                                       member function 
                                       however with base member function  
                                       default value 0 --- size 0 */
    
         return 0;
    }
    
    
     #if 0
     The following shows the outputs:
     17 virtual void Base::resize(int) size 0
     24 virtual void Derived::resize(int) size 3
     24 virtual void Derived::resize(int) size 0
     #endif
    

    【讨论】:

      【解决方案4】:

      通常,使用在特定范围内可见的那些默认参数。你可以做(​​但不应该)时髦的事情:

      #include <iostream>
      void frob (int x) {
          std::cout << "frob(" << x << ")\n";
      }
      
      void frob (int = 0);
      int main () {
          frob();                     // using 0
          {
              void frob (int x=5) ;
              frob();                 // using 5
          }
          {
              void frob (int x=-5) ;
              frob();                 // using -5
          }
      }
      

      在您的情况下,基类签名是可见的。为了使用派生的默认参数,您必须通过指向派生类的指针显式调用该函数,或者以这种方式声明它,或者正确地转换它。

      【讨论】:

        【解决方案5】:

        默认参数值代表调用者传递。从调用者的角度来看,它适用于 B 类(不是 D),因此它通过 10(对于 B 类)

        【讨论】:

          【解决方案6】:

          你的变量是 B 类型的,所以 B 的函数会被调用。要调用 D,您必须将变量声明为 D,或强制转换为 D。

          【讨论】:

          • 由于print成员函数被声明为虚函数,通过基类指针调用将调用成员函数的派生版本,而不是基版本。
          【解决方案7】:

          标准说 (8.3.6.10):

          虚函数调用 (10.3) 使用 中的默认参数 虚函数声明 由静态类型决定 指针或引用表示 目的。一个覆盖函数 派生类不获取默认值 来自函数的参数 it 覆盖。

          这意味着,由于您通过B 类型的指针调用print,它使用B::print 的默认参数。

          【讨论】:

          • Fundoo 回答但有助于理解。谢谢
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-04-01
          • 2014-02-10
          • 1970-01-01
          相关资源
          最近更新 更多