您的一个老客户来找您,需要使用vector来管理众多具有继承关系的类。先有以下三个类。
class Person {
public:
string name;
Person() : name("Person") {}
};
class Male : public Person {
public:
bool taller;
Male() : taller(true) { name = "Male"; }
};
class Female : public Person {
public:
bool taller;
Female() : taller(false) { name = "Female"; }
};
Male及Female类均继承于Person,且与基类Person相比,多了一个taller字段。可别小看了这个简单的taller字段,正是它才会有了此段令人愉悦的经历。此是后话,下文再表。
暂且不管是否有这样的需要,反正客户要求将Person、Male及Female这三个类均放在一个集合中。于是便用vector来管理。客户再提出具体要求,必须使用vector<Person>来统一管理,这样便可应用一些设计模式来简化维护。Design Pattern一书是谁写的?贻害不浅啊!
vector<Person> persons;
Person person;
persons.push_back(person);
Male male1;
persons.push_back(male1);
Male male2;
persons.push_back(male2);
Female female;
persons.push_back(female);
客户说了,我要看这些个人中,哪些人地taller为真。这还不简单吗,直接就可告诉你,Male的taller为真。客户又说了,你说了不算,代码才是本质。客户的文化层次越高,码表劳工的饭碗就越不好端。
“Mmm, select * from persons where taller = ‘true’. ABC…这又不是数据库!”尽管抱怨,迫于3.15的强大监督威力,您被迫妥协。
for (vector<Person>::const_iterator iter = persons.begin(); iter != persons.end(); iter++) {
if (iter->taller) {
…
}
}
慢着,编译器说,taller不是Person的成员。我知道不是,可我也push_back(male1)、push_back(female)了啊。您先搬出RTTI大法验明正身。
for (vector<Person>::const_iterator iter = persons.begin(); iter != persons.end(); iter++) {
cout << typeid(*iter).name() << endl;
}
Person
Person
Person
Person
…
电脑可是一点也不含糊,可也太不通情达理了。在您push_back(male1)、push_back(female)等过程中,由于vector使用了Person模板,电脑已经自动隐式地将Male及Female类型转换为Person类型了。这种向上转换,使得Male及Female的taller字段丢失。我把它的类型转换回Male及FeMale不就行了?
static_cast虽属显式转换,但其最主要的功能还是用于屏蔽编译器进行隐式转换时的警告信息;const_cast只用于加上常量特性;reinterpret_cast虽然功力强大,但它也可将狸猫解释为太子。显然这些都不适用。剩下的只有dynamic_cast了。而dynamic_cast主要是对具有上下级层次结构的指针或引用进行转换。看来这里可以适用,但需要先将vector的模板改为指向Person的指针。
vector<Person*> vpPersons;
Person person;
Person* pPerson = &person;
vpPersons.push_back(pPerson);
Male male1;
Person* pMale = &male1;
vpPersons.push_back(pMale);
Female female;
Female* pFemale = ♀
vpPersons.push_back(pFemale);
但显然,由于vpPersons初始化的代码放在一个代码块中,在堆(heap)上创建的变量会自动消失,只有保存存放于栈(stack)上变量的指针才是安全可靠的。
vector<Person*> vpPersons;
Person* pPerson = new Person();
vpPersons.push_back(pPerson);
Male* pMale1 = new Male();
vpPersons.push_back(pMale1);
Female* pFemale = new Female();
vpPersons.push_back(pFemale);
OK,不是冤家不聚头,new与delete从来就是相儒以沫,骨肉不分离。尽管现在内存很便宜,但内存泄漏的罪名,我们是担当不起的。
for (vector<Person*>::size_type i = 0; i < _vpPersons.size(); i++) {
delete _vpPersons[i];
_vpPersons[i] = NULL;
}
戏台搭好了,好戏要上演了。程序运行得好似正常,但程序结束时电脑总是用一个很低沉的重低音“砰”来打破您美好的幻想,而这种声音无疑也必会将客户的野性完全引发出来。“Something must be wrong.”
问题出在
delete _vpPersons[i];
上面。
我们在通过删除一个指针来释放相应内存,而指针不仅仅指定特定的内存地址,它也是有范围的。指向char型与指向int型的指针虽可同时指向同一地址,但取出内存地址中的内容却是不一样。如果召唤出的是一个一米左右的人,即是小孩;如果是一个一米六七以上的人,那是成人。虽然我们使用vector<Person*>来统一管理众多指针,但C++在保存指针时,还是留了一手,悄悄保留了指针的原型,以保证在取出内容及释放空间时不出任何差错。
在程序中,当删除指针时,C++自动调用dynamic_cast将指针转换回其原有的类型。而对于继承关系的类,dynamic_cast的唯一要求是基类必须有一个虚函数。由于我们的Person类尚未达到此要求,因此,C++(其实是M$)用这种极其特别的方式“提醒”您。显而易见,您需要在Person类中加上一个虚函数。
class Person {
public:
string name;
Person() : name("Person") {}
virtual void f() {}
};
由于该虚函数只用于应付dynamic_cast的特殊要求,因此为节省空间,您将函数体也放在类内定义了。加上该函数后,M$也不再以那种可怕的声音来烦您了。
做了这么多的准备工作,您可以向难缠的客户交差了。
for (vector<Person*>::const_iterator iter = vpPersons.begin(); iter != vpPersons.end(); iter++) {
if (Male* pMale = dynamic_cast<Male*>(*iter)) {
cout << pMale->taller << endl;
}
}
前面的虚函数f()可谓是一箭双雕,为您显式地使用dynamic_cast打下了很好的前提基础。