(一)
注意从 “面向对象的C++” 转向 “模板C++” 时继承可能遭遇问题 :由于基类模板可能被特化,而该特化版本可能会改变成员,因此C++拒绝在模板化基类中寻找继承而来的名称。
(二)
看下面的例子:
假设将信息传送到不同的公司去,传送方式包括明文传送和密文传送,采用模板类的设计方法:
class CompanyA { public: ... void sendClearText(const string& msg); void sendEncrypted(const string& msg); ... }; class CompanyB { public: ... void sendClearText(const string& msg); void sendEncrypted(const string& msg); }; ...//其他公司的classes class MsgInfo {...}; //这个class为将来生产保存信息 template<typename Company> class MsgSender { public: ... void sendClear(const MsgInfo& info) { string msg; ...//根据info产生信息 Company c; c.sendClearText(msg); } void sendSecret(const MsgInfo& info) {...}//这里调用的是c.sendEncrypted };
如果想要添加发送信息时记录信息的功能,采用继承的方式:
template<typename Company> class LoggingMsgSender : public MsgSender<company> { public: ... void sendClearMsg(const MsgInfo& info) { ...//将传送前的信息写至记录信息 sendClear(info);//试图调用基类成员函数完成发送,但是不能通过编译。这是因为在派生类没有具体化时,不知道Company是什么类型,也就不知道基类中是否存在该函数 ...//将传送后的信息写至记录信息 } ... };
编译器不让其通过编译.此时的编译器抛出了"sendClear不存在"的抱怨.可sendClear明显就在base
class内部啊?郁闷,这是为什么倪?别急,我来解释一下:当编译器遇到LoggingMsgSender定义式时,由于Company是一个template参数,不到LoggingMsgSender被具现化的时候,编译器是无法知道Company是什么的.由于无法知道Company是什么,那么它就无法确定MsgSender是什么,也就无法获知其否有个sendClear函数. 停一下,你可能现在对我的解释会有一个问题:为什么根据template Company就不能确定Company是什么了呢?template Company不是有声明式嘛!在这里我提出:我们不能根据一般性模板类的声明式来确定具现化类具有的操作,因为模板类有个特化声明版本的问题.
(三)
改进方法:
(1)在基类函数调用动作之前加上this指针:
template <typename Company> void LoggingMsgSender<Company>::sendClearMsg(const MsgInfo& info){ ... this->sendClear(info); //ok ... }
(2)使用using声明式在类内使用基类命名空间:
template <typename Company> class LoggingMsgSender:public MsgSender<Company>{ public: //这里的情况不是base class名称被derived class名称遮掩,而是编译器不进入base base //作用域查找,于是我们通过using声明式告诉它,请它这么做 using MsgSender<Company>::sendClear;//告诉编译器,请它假设sendClear位于base class内 ... void sendClearMsg(const MsgInfo& info){ ... sendClear(info);//ok ... } };
(3)明确限定函数是基类中的函数:
template <typename Company> class LoggingMsgSender:public MsgSender<Company>{ public: ... void sendClearMsg(const MsgInfo& info){ ... MsgSender<Company>::sendClear(info); //ok ... } ... };
不过此方法有一个很明显的暇疵:如果被调用的是virtual函数,上述的明确资格修饰会关闭"virtual绑定行为".
所以还是尽量选择前两种方法吧。
请记住:
(1)可在derived class template内通过“this->”指涉base class template内的成员名称,或藉由一个明白写出的“base class资格修饰符”完成。