条款43:学习处理模板化基类内的名称
(Know how to access names in templatized base classes.)
内容:
现在我们接到一个编码任务,任务要求我们的目标程序能够传送信息到不同的公司去.这里的信息可以分为:被译成密码的信息和未经加工信息明文信息.我们分析了任务以后认为,在目标程序的编译期间我们就可以决定哪一个信息传送至哪一家公司.所以我们采用了template来实现,大概的伪代码实现如下:
class CompanyA{
public:
...
void sendClearText(const std::string& msg);
void sendEncrypted(const std::string& msg);
...
};
class CompanyB{
public:
...
void sendClearText(const std::string& msg);
void sendEncrypted(const std::string& msg);
...
};
... //其它公司的classes.
class MsgInfo{...};//这个class为将来生产保存信息
template<typename Company>
class MsgSender{
public:
...
void sendClear(const MsgInfo& info){
std::string msg;
...//根据info产生信息
Company c;
c.sendClearText(msg);
}
void sendSecret(const MsgInfo& info){...} //这里调用的是c.sendEncrypted.
};
上面的设计属于一个良好的设计.现在我们又需要另外一种MsgSender,它的特殊之处在于,在每次发送信息时,它能够log某些信息.显然将这个类视为MsgSender的一个子类将是一个不错的选择:
template <typename Company>
class LoggingMsgSender:public MsgSender<Comany>{
public:
...
void sendClearMsg(const MsgInfo& info){ //为避免"名称遮掩"现象的发生,采用了一个不同的名称
...// record status information before sending message
sendClear(info);
...//record status information after sending message.
}
...
};
这样的代码看起来没用什么可以指责的地方,但编译器却不让其通过编译.此时的编译器抛出了"sendClear不存在"的抱怨.可sendClear明显就在base class内部啊?郁闷,这是为什么倪?别急,我来解释一下:当编译器遇到LoggingMsgSender定义式时,由于Company是一个template参数,不到LoggingMsgSender被具现化的时候,编译器是无法知道Company是什么的.由于无法知道Company是什么,那么它就无法确定MsgSender是什么,也就无法获知其否有个sendClear函数. 停一下,你可能现在对我的解释会有一个问题:为什么根据template Company就不能确定Company是什么了呢?template Company不是有声明式嘛!在这里我提出:我们不能根据一般性模板类的声明式来确定具现化类具有的操作,因为模板类有个特化声明版本的问题.为了让问题具体化,假设现在有另外一个CompanyZ坚持使用加密通讯:
class CompanyZ{ //这个class 不提供sendClearText函数
public:
...
void sendEncrypted(const std::string& msg);
...
};
现在的问题是:一般性的模板类MsgSender声明对CompanyZ并不适用,因为那个template提供了一个sendClear函数(内部调用sendClearText函数),而这对CompanyZ对象不合理.欲矫正这个问题,我们可以针对CompanyZ产生一个MsgSender特化版:
template<>
class MsgSender<CompanyZ>{
public:
...
void sendSecret(const MsgInfo& info){...} //只提供sendSecret方法
...
};
好了,我们再次来看LoggingMsgSender::sendClearMsg:
tempalte <typename Company>
void LoggingMsgSend<Company>::(const MsgInfo& info){
...//
sendClear(info); //如果这里Company == CompanyZ,这个函数就不存在
...//
}
哒哒哒哒,问题来了吧!这就是C++拒绝这个调用的原因:它知道base class template有可能被特化,而那个特化版本可能不提供和一般性template相同的接口.就某种意义而已,当我们从Object Oriented C++跨进Template C++,继承就不像以前那么畅行无阻了.
麻烦点就麻烦点,问题终归还是要解决的.我们有以下三个方法来解决此类问题:
方法一:在base class函数调用动作之前加上"this->":
template <typename Company>
void LoggingMsgSender<Company>::sendClearMsg(const MsgInfo& info){
...
this->sendClear(info); //ok
...
}
方法二:使用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
...
}
};
方法三:明明白白指出被调用函数位于base class内:
template <typename Company>
class LoggingMsgSender:public MsgSender<Company>{
public:
...
void sendClearMsg(const MsgInfo& info){
...
MsgSender<Company>::sendClear(info); //ok
...
}
...
};
不过此方法有一个很明显的暇疵:如果被调用的是virtual函数,上述的明确资格修饰会关闭"virtual绑定行为".
根本而言,本条款探讨的是,面对"指涉base class members"之无效references,编译器的诊断时间可能发生在早期(当解析derived class template的定义式时),也可能发生在晚期(当那些template被特定之template实参具现化时).C++的政策是宁愿较早诊断,这就是为什么"当base classes从template 中被具现化时"它假设它对那些base classes的内容毫无所悉的缘故.
请记住:
■ 可在derived class templates内通过"this->"指涉base class templates内的成员名称,或由一个明白写出"base class资格修饰符"完成.