65.9K
CodeProject 正在变化。 阅读更多。
Home

在编码前思考,C++ 中的虚函数

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.58/5 (134投票s)

2008年9月2日

CPOL

2分钟阅读

viewsIcon

92392

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() 方法。

这个著名的属性被称为对象切片。 从一个对象中剪切出所需的属性并将其复制到具体的基类对象中。

参考文献

  1. C++ 编程语言:Bjarne Stroustrup
© . All rights reserved.