关于c++默认参数是否可以继承这个问题。我们先给出答案是,不可继承并且静态绑定为当前类接口的默认参数(如果有声明的话)上。当然,我们不单只需要知道结果,还应该探索一下原因。
考虑以下代码。非常简单的一段测试代码。有个IServer基类,其listen接口有个默认参数。IServer派生出两个子类CRPCServer与CHTTPServer,两个子类在其listen接口上绑定不同的默认参数。
#include <unistd.h>
#include <stdio.h>
#include <stdint.h>
enum
{
BACKLOG_DEFULT = 128,
BACKLOG_MEDIUM = 1024,
BACKLOG_BIGGER = 2048
};
class IServer
{
public:
virtual ~IServer(){};
virtual int listen(int backlog = BACKLOG_DEFULT) = 0;
};
class CRPCServer: public IServer
{
public:
~CRPCServer(){};
int listen(int backlog = BACKLOG_MEDIUM)
{
printf("IRPCServer listen backlog: %d\n", backlog);
return 0;
}
};
class CHTTPServer: public IServer
{
public:
~CHTTPServer(){};
int listen(int backlog = BACKLOG_BIGGER)
{
printf("CHTTPServer listen backlog: %d\n", backlog);
return 0;
}
};
int main(int argc, char *argv[])
{
IServer *irpcserver = new CRPCServer;
IServer *ihttpserver = new CHTTPServer;
irpcserver->listen();
ihttpserver->listen(0x1000);
delete irpcserver; irpcserver = NULL;
delete ihttpserver; ihttpserver = NULL;
return 0;
}
执行结果如下,可见irpcserver虽然真正指向对象类型是CRPCServer。但是其listen接口默认参数是绑定到IServer::listen的默认参数上的。也就是说默认参数不具备多态,也不具备可继承性。它是通过编译器静态绑定到当前对象的接口上的默认参数。倘若要实现多态,那么其中普遍做法就是放入虚表中,那么复杂性以及管理又会变得复杂。
IRPCServer listen backlog: 128
CHTTPServer listen backlog: 4096
接下来我们来看默认参数,编译器是怎么处理的。部分分析涉及多态原理,可以参考另外一篇博客<深入理解c++多态实现原理>。我们对main函数反汇编后提取两次调用listen来看。可见irpcserver调用listen时是将128入栈传参,即绑定到IServer::listen的默认参数。ihttpserver调用listen时则传入了我们指定的非默认参数。
Dump of assembler code for function main(int, char**):
...
0x080485c2 <+62>: mov 0x18(%esp),%eax # 将irpcserver放入eax寄存器
0x080485c6 <+66>: mov (%eax),%eax # 取出irpcserver的vptr虚表指针
0x080485c8 <+68>: add $0x8,%eax # 偏移掉vptr中两个析构函数
0x080485cb <+71>: mov (%eax),%edx # 取出虚表中irpcserver函数地址
0x080485cd <+73>: movl $0x80,0x4(%esp) # 0x80(128)入栈
0x080485d5 <+81>: mov 0x18(%esp),%eax
0x080485d9 <+85>: mov %eax,(%esp) # irpcserver指针入栈
0x080485dc <+88>: call *%edx # 调用listen函数
0x080485de <+90>: mov 0x1c(%esp),%eax # 将ihttpserver放入eax寄存器
0x080485e2 <+94>: mov (%eax),%eax # 取出ihttpserver的vptr虚表指针
0x080485e4 <+96>: add $0x8,%eax # 偏移掉vptr中两个析构函数
0x080485e7 <+99>: mov (%eax),%edx # 取出虚表中listen函数地址
0x080485e9 <+101>: movl $0x1000,0x4(%esp) # 0x1000入栈
0x080485f1 <+109>: mov 0x1c(%esp),%eax
0x080485f5 <+113>: mov %eax,(%esp) # ihttpserver指针入栈
0x080485f8 <+116>: call *%edx # 调用listen函数
...
总结:
1) 默认参数不可继承,也不具备多态,其绑定到当前对象接口的默认参数上
2) 倘若要加入默认参数,最好基类与子类保持一致,避免看起来传入不同默认参数的怪异的行为。