C++类模板最早被使用在容器中,随着C++应用场景的不断扩展,C++类模板的作用得到了极大的丰富。
多态是C++一个重要的特性,我们一般将多态分为“编译期多态”和“运行期多态”,这两个对多态的划分,源自于对“哪一个重载函数该被调用(编译期)”和“哪一个virtual函数将被绑定(运行期)”这两个问题的思考。对于类模板,由于模板参数的不确定性,引出了很多编译器无法知晓的参数类型,看看下面这段代码:
template<class T>
class CBufferBase
{
public:
CBufferBase(void){}
virtual ~CBufferBase(void){}
typedef std::list<T*> PackList;
typedef PackList::iterator PackIterator; //错误
......
};
以上代码将无法编译通过。正如我们所知,T是一个不确定的模板参数,这导致了PackList也是一个不确定的对象(或者参数等等,尽管std::list是我们熟知的容器),最终导致了PackList::iterator对于编译器来说是一个未知的对象,然后通过这个位置的对象去声明一个实例,肯定不符合C++的语法规则。为了解决上述问题,我们必须告诉编译器:PackList::iterator是一个类型。对上述代码做如下修改:
template<class T>
class CBufferBase
{
......
typedef typename PackList::iterator PackIterator;
......
};
通过紧邻PackList::iterator之前加上typename告诉编译器它是一个类型。
看看下面这个场景:一个汽车销售代理商目前有若干燃油车和电动车待销售,客户来店看车的时候,工作人员需要调出客户感兴趣车型的信息,其中有一些信息,燃油车和电动车的信息类型不一样,比如:燃油车的油箱容量和电动车的电池容量,调出信息之后将所有信息打印出来,看看实现代码:
//燃油车
class FuelVehicle
{
......
public:
void GetCapacity() {}; //获取油箱容量
};
//电动车
class ElecVehicle
{
......
public:
void GetCapacity() {}; //获取电池容量
private:
};
//打印信息
template<typename Vehicle>
class PrintVehicleInfo
{
......
public:
void PrintInfo()
{
Vehicle V;
V.GetCapacity(); //获取容量
//......
}
};
上面的代码看起来一切正常。假设此时我们需要将每次打印的信息保存到电脑数据库,我们从上述代码中派生一个类,可以轻松解决这个需求:
template<typename Vehicle>
class SaveVehicleInfo : public PrintVehicleInfo<Vehicle>
{
......
public:
//打印并保存汽车信息
void PrintAndSaveInfo()
{
PrintInfo();//错误
......
}
};
这个派生类中定义了一个函数PrintAndSaveInfo(),在基类中没有同名称的函数,这是一个好的设计。上述这段代码将无法编译通过,原因是编译器找不到函数PrintInfo()的定义,虽然我们用眼睛可以看到PrintInfo()就在基类中,但是编译器就是看不到,这是上面原因呢?
问题是这样的,对于派生类SaveVehicleInfo ,编译器并不知道它继承哪个class。也许你会说是PrintVehicleInfo,但是PrintVehicleInfo中的Vehicle是一个模板参数,在SaveVehicleInfo被具现化之前,编译器不知道Vehicle具体是什么,从而也就不知道PrintVehicleInfo<Vehicle>具体是什么,因而无法确定它是否有函数PrintInfo(),最终编译失败。
我们必须让编译器避免观察“被模板化的基类”。解决的办法主要有两个,一是在调用的基类函数前面加上this->:
......
void PrintAndSaveInfo()
{
this->PrintInfo();
......
}
二是使用using声明式:
......
using PrintVehicleInfo::PrintInfo;
void PrintAndSaveInfo()
{
PrintInfo();
......
}
对于调用基类成员函数无效的问题,编译器的诊断时间发生在早期(也就是在解析派生类模板的时候),也有可能是在晚期(当模板参数被具体的实参具现化的时候),C++的思想是尽早诊断。出现此类问题,我们应告之编译器的具体调用路径。