虚函数默认参数行为

Posted

技术标签:

【中文标题】虚函数默认参数行为【英文标题】:virtual function default arguments behaviour 【发布时间】:2011-06-24 06:52:45 【问题描述】:

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

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 ]

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

【问题讨论】:

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

标准说 (8.3.6.10):

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

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

【讨论】:

Fundoo 回答但有助于理解。谢谢【参考方案2】:

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

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

【讨论】:

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

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

#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
    

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

【讨论】:

【参考方案4】:

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

【讨论】:

【参考方案5】:

动态绑定使用 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

【讨论】:

【参考方案6】:

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

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

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

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

【讨论】:

【参考方案7】:

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

【讨论】:

由于print成员函数被声明为虚函数,通过基类指针调用将调用成员函数的派生版本,而不是基版本。

以上是关于虚函数默认参数行为的主要内容,如果未能解决你的问题,请参考以下文章

Item 37:不要重写父类函数的默认参数

当 slot 函数具有默认参数=None 时,PySide2 的行为与 PySide 不同

为啥显式允许默认构造函数和具有 2 个或更多(非默认)参数的构造函数?

C++中父类和子类的成员函数同名同参数同返回只是父类里面是虚函数,子类没有虚函数限定 有问题吗

在 R 中设置函数参数的默认值

Hive 窗口分析函数