虚函数默认参数行为
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->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
成员函数被声明为虚函数,通过基类指针调用将调用成员函数的派生版本,而不是基版本。以上是关于虚函数默认参数行为的主要内容,如果未能解决你的问题,请参考以下文章
当 slot 函数具有默认参数=None 时,PySide2 的行为与 PySide 不同
为啥显式允许默认构造函数和具有 2 个或更多(非默认)参数的构造函数?