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); }