在编码前思考,C++ 中的虚函数
C++ 中的虚函数。
引言
几天前,我在工作,无意中在代码中犯了一个错误(什么错误?我将在文章的详细部分解释),当我被一个 bug 困住并开始调试时,我很惊讶于一个小的错误竟然能给程序员带来如此多的痛苦。是的,我在虚函数领域犯了一个错误。怎么犯的?让我们来找出答案........
使用代码
那么,我们为什么需要虚函数呢? 每个人都知道。 假设我有一个基类和几个派生类; 并且所有派生类共享一个公共函数,在驱动程序中,我不想编写一个庞大的 switch
/if
块。 我想遍历所有派生类型并希望执行公共成员函数。 像这样
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
using std::vector;
class CommunicationDevices
{
//Base class has some property, for this article I dont need those
public:
inline virtual void which(){
cout<<"This is a common device..."<<endl;
}
};
class MobilePhoneWithGSMSupport:public CommunicationDevices
{
//Derived class also has some extended property, GSM related
public:
inline virtual void which(){
cout<<"This is a Mobile Phone...GSM Supported"<<endl;
}
};
class MobilePhoneWithCDMASupport:public CommunicationDevices
{
//Derived class also has some extended property, CDMA related
public:
inline void which(){
cout<<"This is a Mobile Phone....CDMA Supported"<<endl;
}
};
class Landline:public CommunicationDevices
{
//Derived class also has some extended property
public:
inline void which(){
cout<<"This is a Landline Phone..."<<endl;
}
};
class Iphone:public MobilePhoneWithGSMSupport
{
//More specific IPhone Feature here
public:
inline void which(){
cout<<"This is apple Iphone with AT&T connection, GSM Support only..."
<<endl;
}
};
void whichPhoneUserIsUsing(CommunicationDevices &devices){
devices.which();
}
int main(){
MobilePhoneWithGSMSupport user1;
MobilePhoneWithCDMASupport user2;
Landline user3;
Iphone user4;
whichPhoneUserIsUsing(user1);
whichPhoneUserIsUsing(user2);
whichPhoneUserIsUsing(user3);
whichPhoneUserIsUsing(user4);
return 0;
}
在这里,想法很简单。 由于我们在基类中使用虚函数,“whichPhoneUserIsUsing()
” 方法可以接受一个通用的基类参数,并且根据对象的实际类型访问来自派生类的适当方法。 这就是虚函数的妙处。 请注意,在方法 “whichPhoneUserIsUsing()
” 中,我们使用了一个对基类的引用作为参数: “CommunicationDevices &devices
”,并且在驱动程序(main()
)中,我们在调用此函数时传递了派生类的对象。 这通常称为 C++ 中的向上转型。 也就是说,我们从更具体的类型转到更通用的类型。 而且,这种转换总是类型安全的。 正如您所期望的,此代码将产生以下 o/p
bash-3.2$ g++ -g -o hello code1.cpp
bash-3.2$ ./hello
This is a Mobile Phone...GSM Supported
This is a Mobile Phone....CDMA Supported
This is a Landline Phone...
This is apple Iphone with AT&T connection, GSM Support only...
现在,考虑以下代码,此处仅更改了一个字符(相信我,只有一个字符),与之前的代码相比
我们只是这样修改了我们的方法 whichPhoneUserIsUsing()
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
using std::vector;
class CommunicationDevices
{
//Base class has some property, for this article I dont need those
public:
inline virtual void which(){
cout<<"This is a common device..."<<endl;
}
};
class MobilePhoneWithGSMSupport:public CommunicationDevices
{
//Derived class also has some extended property, GSM related
public:
inline virtual void which(){
cout<<"This is a Mobile Phone...GSM Supported"<<endl;
}
};
class MobilePhoneWithCDMASupport:public CommunicationDevices
{
//Derived class also has some extended property, CDMA related
public:
inline void which(){
cout<<"This is a Mobile Phone....CDMA Supported"<<endl;
}
};
class Landline:public CommunicationDevices
{
//Derived class also has some extended property
public:
inline void which(){
cout<<"This is a Landline Phone..."<<endl;
}
};
class Iphone:public MobilePhoneWithGSMSupport
{
//More specific IPhone Feature here
public:
inline void which(){
cout<<"This is apple Iphone with AT&T connection, GSM Support only..."
<<endl;
}
};
void whichPhoneUserIsUsing(CommunicationDevices devices){
devices.which();
}
int main(){
MobilePhoneWithGSMSupport user1;
MobilePhoneWithCDMASupport user2;
Landline user3;
Iphone user4;
whichPhoneUserIsUsing(user1);
whichPhoneUserIsUsing(user2);
whichPhoneUserIsUsing(user3);
whichPhoneUserIsUsing(user4);
return 0;
}
我们只是这样修改了我们的方法 whichPhoneUserIsUsing()
void whichPhoneUserIsUsing(CommunicationDevices devices){
devices.which();
}
然后砰的一声.................下面是输出
bash-3.2$ g++ -g -o hello code2.cpp
bash-3.2$ ./hello
This is a common device...
This is a common device...
This is a common device...
This is a common device...
bash-3.2$ vim code2.cpp
那么,这里出了什么问题?
是的,你猜对了,这是一个著名的复制构造函数问题。 当参数仅仅是一个 “CommunicationDevices
” 而不是对其的引用时,函数会说
嘿,程序员先生,我注定要为这个函数(whichPhoneUserIsUsing()
)只创建一个临时对象。 我不再负责获取引用,所以我不关心你传递的是什么样的实际对象; 我将创建一个具体的 “CommunicationDevices
” 对象,并且只复制实际对象中对我来说有意义的那些段(即,属于基类的那部分)。 并且,将仅为此临时对象调用 “which
” 方法。 因此,每次你调用我时,我都会调用基类版本(即,CommunicationDevices
版本)的 which()
方法。
这个著名的属性被称为对象切片。 从一个对象中剪切出所需的属性并将其复制到具体的基类对象中。
参考文献
- C++ 编程语言:Bjarne Stroustrup