C++ 虚函数默认参数行为

C++ 虚函数默认参数行为,c++,C++,我对下面的代码有一个奇怪的情况。请帮我把它弄清楚 class B { public: B(); virtual void print(int data=10) { cout << endl << "B--data=" << data; } }; class D:public B { public:

我对下面的代码有一个奇怪的情况。请帮我把它弄清楚

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=10 ]

请帮忙。这对您来说可能很明显,但我不知道内部机制。

标准中说8.3.6.10:

虚拟函数调用10.3使用 中的默认参数 虚函数的声明 由系统的静态类型决定 表示该对象的指针或引用 对象函数中的重写函数 派生类不获取默认值 函数的参数 覆盖


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

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

将代表调用方传递默认参数值。从调用方的角度来看,它与类B而不是D一起工作,因此它通过了10。对于类B,通常使用在特定范围内可见的默认参数。你可以做但不应该做一些古怪的事情:

#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
    }
}

在您的例子中,基类签名是可见的。为了使用派生的默认参数,必须通过指向派生类的指针显式调用该函数,方法可以是这样声明,也可以是正确地强制转换。

默认参数完全是编译时特性。也就是说,在编译时执行默认参数替代缺少的参数。因此,显然,成员函数的默认参数选择无法依赖于对象的动态类型,即运行时类型。它始终取决于静态对象,即对象的编译时类型


编译器会立即将您在代码示例中编写的调用解释为bp->print10,而不考虑其他任何内容。

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

因此,默认参数是在编译时静态确定的。在本例中,它是由bp类型静态确定的,bp类型是指向基类的指针。因此,data=10作为函数参数传递,而函数指针指向派生类成员函数:D::print。本质上,它调用D::print10

下面的代码段和结果输出清楚地说明了这一点:即使它调用派生调用成员函数Derived::resizeint,它也会传递基类默认参数:size=0

虚拟空派生::resizeint大小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

基本上,当你用一个像这样的默认参数来声明一个函数时,你会隐式地声明和定义一个内联重载,其中少了一个参数,这个参数值只调用完整的函数。问题是,这个额外的重载函数不是虚拟的,即使该函数是虚拟的。因此,您在B中定义的函数等价于:

        virtual void print(int data)
        {
              cout << endl << "B--data=" << data;
        }
        void print() { print(10); }
这意味着,当您在没有参数的情况下调用print时,您得到的函数基于静态类型B,以防您感到困惑。然后调用printint,它是虚拟的,因此使用动态类型


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

感谢Andrey澄清了我的疑问。我没有朝这个方向想。非常感谢。Fundoo回答,但有助于理解。感谢如果答案解决了您的问题或使您理解了问题,请使用答案左侧的绿色勾号接受答案。由于打印成员函数声明为虚拟函数,因此通过基类指针调用将调用成员函数的派生版本,而不是基版本。
        virtual void print(int data)
        {
              cout << endl << "B--data=" << data;
        }
        void print() { print(10); }