今天看到设计模式的模板方法模式, 该模式中模板方法在基类中定义一个操作的算法骨架, 将一些重要步骤的实现延迟子类中, 使得可以通过生成子类重定义算法的重要步骤. 由于模板方法定义了算法的骨架, 若父类中定义了一些对于子类有用的操作, 并且这些操作不需要子类实现, 那该方法在C++中可定义为非虚函数, 那如果子类重载了该函数, 而父类的模板方法中使用了该基类的对应函数并且没有加上作用域限制符是否会影响到模板方法的行为?带着这个问题我们看看以下代码:
class Base{
public:
void NotOverloaded()
{
Overloaded();
}
void Overloaded()
{
printf("Base::Overloaded!/n");
}
};
class Child:public Base{
public:
void Overloaded()
{
printf("Child::Overloaded!/n");
}
};
int main(int argc, char* argv[])
{
Child ch;
ch.NotOverloaded();
return 0;
}
程序输出是什么呢?我原以为Child从Base继承得到NotOverloaded()方法, 那NotOverloaded()方法中调用的成员Overloaded()(实际为this->Overloaded()), 默认的this指针应该指向当前的Child的实例, 那实际应该调用Child::Overloaded(), 输出为"Child::Overloaded!"; 然而实际输出为"Base::Overloaded!", 让我十分不解, 于是查看了该代码的汇编代码.
VC6.0下生成的汇编代码片断如下:
C++: ch.NotOverloaded()
ASM: 00401048 lea ecx,[ebp-4]
0040104B call @ILT+0(Base::NotOverloaded) (00401005)
此处直调用Base::NotOverloaded()
调用Base::NotOverloaded()方法时跳转相应的代码段:
@ILT+0(?NotOverloaded@Base@@QAEXXZ):
00401005 jmp Base::NotOverloaded (00401080)
在Base::NotOverloaded()方法中调用Overloaded(), 其汇编代码如下:
0040109D mov ecx,dword ptr [ebp-4]
004010A0 call @ILT+35(Child::Child) (00401028)
此处调用Base::Overloaded():
@ILT+10(??0Child@@QAE@XZ):
00401028 jmp Base::Overloaded (004010e0)
可见, 由于Overloaded()并不是虚函数, 在编译时即指定了相应的函数的地址, 在Base::NotOverloaded()中调用Overloaded()时, 在该环境下Overloaded()已指定为Base::Overloaded(), Child从Base继承了该Overloaded和NotOverloaded, 并重载了Overloaded(), 然而调用NotOverloaded()时进入的代码段为父类Base相应方法的代码段, 由于编译时已确定了NotOverloaded()中调用的非虚函数的地址(由于Overloaded方法非虚函数, 编译器不知道也不会为子类对该函数的重载进行动态的绑定), 因此该处将调用父类的Base::Overloaded()得到上述的输出结果.
若将类Base的Overloaded()改为虚函数:
class Base{
public:
void NotOverloaded()
{
Overloaded();
}
virtual void Overloaded()
{
printf("Base::Overloaded!/n");
}
};
调用子类Child实例的NotOverloaded()方法
29: ch.NotOverloaded();
00401070 lea ecx,[ebp-4]
00401073 call @ILT+5(Base::NotOverloaded) (0040100a)
跳转到Base::NotOverloaded()
@ILT+5(?NotOverloaded@Base@@QAEXXZ):
0040100A jmp Base::NotOverloaded (004010b0)
在Base::NotOverloaded()中调用Overloaded(), 其汇编代码如下
10: Overloaded();
004010CD mov eax,dword ptr [ebp-4]
004010D0 mov edx,dword ptr [eax] //此时: eax = 0012FF7C = this
//dword ptr[eax] = 0042201C = this->__vfptr[0]
//获取虚表的首地址
004010D2 mov esi,esp
004010D4 mov ecx,dword ptr [ebp-4]
004010D7 call dword ptr [edx] //此时: edx = this->__vfptr[0] = Child::Overloaded()
//dword ptr [edx] = 00401005
//调用虚表指定位置的虚函数
//@ILT+0(?Overloaded@Child@@UAEXXZ):
//00401005 jmp Child::Overloaded (00401160)
004010D9 cmp esi,esp
004010DB call __chkesp (00401250)
由于虚函数是动态绑定的, 在编译时将类的虚函数地址填入相应类的虚表, 在运行时通过虚表中相应函数的地址调用正确的虚函数.因此不存在本文前面的情况, 其输出为"Child::Overloaded!".
class Base{
public:
void NotOverloaded()
{
Overloaded();
}
void Overloaded()
{
printf("Base::Overloaded!/n");
}
};
class Child:public Base{
public:
void Overloaded()
{
printf("Child::Overloaded!/n");
}
};
int main(int argc, char* argv[])
{
Child ch;
ch.NotOverloaded();
return 0;
}
程序输出是什么呢?我原以为Child从Base继承得到NotOverloaded()方法, 那NotOverloaded()方法中调用的成员Overloaded()(实际为this->Overloaded()), 默认的this指针应该指向当前的Child的实例, 那实际应该调用Child::Overloaded(), 输出为"Child::Overloaded!"; 然而实际输出为"Base::Overloaded!", 让我十分不解, 于是查看了该代码的汇编代码.
VC6.0下生成的汇编代码片断如下:
C++: ch.NotOverloaded()
ASM: 00401048 lea ecx,[ebp-4]
0040104B call @ILT+0(Base::NotOverloaded) (00401005)
此处直调用Base::NotOverloaded()
调用Base::NotOverloaded()方法时跳转相应的代码段:
@ILT+0(?NotOverloaded@Base@@QAEXXZ):
00401005 jmp Base::NotOverloaded (00401080)
在Base::NotOverloaded()方法中调用Overloaded(), 其汇编代码如下:
0040109D mov ecx,dword ptr [ebp-4]
004010A0 call @ILT+35(Child::Child) (00401028)
此处调用Base::Overloaded():
@ILT+10(??0Child@@QAE@XZ):
00401028 jmp Base::Overloaded (004010e0)
可见, 由于Overloaded()并不是虚函数, 在编译时即指定了相应的函数的地址, 在Base::NotOverloaded()中调用Overloaded()时, 在该环境下Overloaded()已指定为Base::Overloaded(), Child从Base继承了该Overloaded和NotOverloaded, 并重载了Overloaded(), 然而调用NotOverloaded()时进入的代码段为父类Base相应方法的代码段, 由于编译时已确定了NotOverloaded()中调用的非虚函数的地址(由于Overloaded方法非虚函数, 编译器不知道也不会为子类对该函数的重载进行动态的绑定), 因此该处将调用父类的Base::Overloaded()得到上述的输出结果.
若将类Base的Overloaded()改为虚函数:
class Base{
public:
void NotOverloaded()
{
Overloaded();
}
virtual void Overloaded()
{
printf("Base::Overloaded!/n");
}
};
调用子类Child实例的NotOverloaded()方法
29: ch.NotOverloaded();
00401070 lea ecx,[ebp-4]
00401073 call @ILT+5(Base::NotOverloaded) (0040100a)
跳转到Base::NotOverloaded()
@ILT+5(?NotOverloaded@Base@@QAEXXZ):
0040100A jmp Base::NotOverloaded (004010b0)
在Base::NotOverloaded()中调用Overloaded(), 其汇编代码如下
10: Overloaded();
004010CD mov eax,dword ptr [ebp-4]
004010D0 mov edx,dword ptr [eax] //此时: eax = 0012FF7C = this
//dword ptr[eax] = 0042201C = this->__vfptr[0]
//获取虚表的首地址
004010D2 mov esi,esp
004010D4 mov ecx,dword ptr [ebp-4]
004010D7 call dword ptr [edx] //此时: edx = this->__vfptr[0] = Child::Overloaded()
//dword ptr [edx] = 00401005
//调用虚表指定位置的虚函数
//@ILT+0(?Overloaded@Child@@UAEXXZ):
//00401005 jmp Child::Overloaded (00401160)
004010D9 cmp esi,esp
004010DB call __chkesp (00401250)
由于虚函数是动态绑定的, 在编译时将类的虚函数地址填入相应类的虚表, 在运行时通过虚表中相应函数的地址调用正确的虚函数.因此不存在本文前面的情况, 其输出为"Child::Overloaded!".