虚函数默认参数行为

时间:2011-06-24 06:52:46

标签: c++

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

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 ]

请帮忙。这对您来说似乎很明显,但我不了解内部机制。

7 个答案:

答案 0 :(得分:32)

标准说(8.3.6.10):

  

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

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

答案 1 :(得分:25)

默认参数完全是编译时功能。即在编译时执行默认参数替换缺少参数的替换。因此,显然,成员函数的默认参数选择无法依赖于对象的动态(即运行时)类型。它总是依赖于对象的 static (即编译时)类型。

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

答案 2 :(得分: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
    }
}

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

答案 3 :(得分:1)

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

答案 4 :(得分:0)

您的变量是B类型,因此将调用B的函数。要调用D,您必须将变量声明为D,或者转换为D。

答案 5 :(得分:0)

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

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

以下代码片段和结果输出清楚地说明了这一点:即使它调用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 :(得分:0)

基本上,当您使用这样的默认参数声明函数时会发生什么,您(隐式)声明和定义了一个内联重载,其中少了一个参数,该参数仅调用具有该参数值的完整函数。问题是,这个额外的重载函数不是虚拟的,即使该函数是。所以你在 B 中定义的函数等价于:

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

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

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